diff options
615 files changed, 13388 insertions, 4258 deletions
diff --git a/api/gen_combined_removed_dex.sh b/api/gen_combined_removed_dex.sh index e0153f7c1091..2860e2ed17c2 100755 --- a/api/gen_combined_removed_dex.sh +++ b/api/gen_combined_removed_dex.sh @@ -6,6 +6,6 @@ shift 2 # Convert each removed.txt to the "dex format" equivalent, and print all output. for f in "$@"; do - "$metalava_path" signature-to-dex "$f" "${tmp_dir}/tmp" + "$metalava_path" signature-to-dex "$f" --out "${tmp_dir}/tmp" cat "${tmp_dir}/tmp" done diff --git a/core/api/current.txt b/core/api/current.txt index 44a6c6b2803b..5456c15040ce 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -43901,6 +43901,7 @@ package android.telephony { field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool"; field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool"; field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool"; field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 0ab2588dd87e..e4a840745ab3 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -318,6 +318,14 @@ package android.net.netstats { } +package android.net.wifi { + + public final class WifiMigration { + method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration") public static void migrateLegacyKeystoreToWifiBlobstore(); + } + +} + package android.nfc { public class NfcServiceManager { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b3c471c461be..cd2c9ca55959 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3447,6 +3447,7 @@ package android.companion.virtual { } public static interface VirtualDeviceManager.ActivityListener { + method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onActivityLaunchBlocked(int, @NonNull android.content.ComponentName, int); method public void onDisplayEmpty(int); method @Deprecated public void onTopActivityChanged(int, @NonNull android.content.ComponentName); method public default void onTopActivityChanged(int, @NonNull android.content.ComponentName, int); @@ -3523,6 +3524,7 @@ package android.companion.virtual { field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1 field @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3 field public static final int POLICY_TYPE_AUDIO = 1; // 0x1 + field @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final int POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR = 6; // 0x6 field @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final int POLICY_TYPE_CAMERA = 5; // 0x5 field @FlaggedApi("android.companion.virtual.flags.cross_device_clipboard") public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4 field public static final int POLICY_TYPE_RECENTS = 2; // 0x2 @@ -11399,8 +11401,9 @@ package android.os.connectivity { method public long getNumPacketsTx(); method public long getRxTimeMillis(); method public long getSleepTimeMillis(); - method @NonNull public long getTimeInRatMicros(int); - method @NonNull public long getTimeInRxSignalStrengthLevelMicros(@IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) int); + method public long getTimeInRatMicros(int); + method public long getTimeInRxSignalStrengthLevelMicros(@IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) int); + method @FlaggedApi("com.android.server.power.optimization.streamlined_connectivity_battery_stats") public long getTxTimeMillis(@IntRange(from=android.telephony.ModemActivityInfo.TX_POWER_LEVEL_0, to=android.telephony.ModemActivityInfo.TX_POWER_LEVEL_4) int); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.os.connectivity.CellularBatteryStats> CREATOR; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 1352465b892d..b7de93aaaee3 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2608,6 +2608,18 @@ package android.os { } +package android.os.connectivity { + + public final class CellularBatteryStats implements android.os.Parcelable { + ctor @FlaggedApi("com.android.server.power.optimization.streamlined_connectivity_battery_stats") public CellularBatteryStats(long, long, long, long, long, long, long, long, long, long, @NonNull long[], @NonNull long[], @NonNull long[], long); + } + + public final class WifiBatteryStats implements android.os.Parcelable { + ctor @FlaggedApi("com.android.server.power.optimization.streamlined_connectivity_battery_stats") public WifiBatteryStats(long, long, long, long, long, long, long, long, long, long, long, long, long, @NonNull long[], @NonNull long[], @NonNull long[], long); + } + +} + package android.os.health { public class HealthKeys { diff --git a/core/java/Android.bp b/core/java/Android.bp index fae411d495ca..128fb62e21c9 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -20,10 +20,43 @@ filegroup { "**/*.java", "**/*.aidl", ":framework-nfc-non-updatable-sources", + ":messagequeue-gen", + ], + // Exactly one of the below will be added to srcs by messagequeue-gen + exclude_srcs: [ + "android/os/LegacyMessageQueue/MessageQueue.java", + "android/os/ConcurrentMessageQueue/MessageQueue.java", + "android/os/SemiConcurrentMessageQueue/MessageQueue.java", ], visibility: ["//frameworks/base"], } +// Add selected MessageQueue.java implementation to srcs +soong_config_module_type { + name: "release_package_messagequeue_implementation_srcs", + module_type: "genrule", + config_namespace: "messagequeue", + value_variables: ["release_package_messagequeue_implementation"], + properties: [ + "srcs", + ], +} + +// Output the selected android/os/MessageQueue.java implementation +release_package_messagequeue_implementation_srcs { + name: "messagequeue-gen", + soong_config_variables: { + release_package_messagequeue_implementation: { + srcs: ["android/os/%s"], + conditions_default: { + srcs: ["android/os/LegacyMessageQueue/MessageQueue.java"], + }, + }, + }, + cmd: "mkdir -p android/os/; cp $(in) $(out);", + out: ["android/os/MessageQueue.java"], +} + aidl_library { name: "IDropBoxManagerService_aidl", srcs: [ diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2313fa27afe1..5214d2c9c02a 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2981,9 +2981,7 @@ public class AppOpsManager { new AppOpInfo.Builder(OP_ESTABLISH_VPN_MANAGER, OPSTR_ESTABLISH_VPN_MANAGER, "ESTABLISH_VPN_MANAGER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), new AppOpInfo.Builder(OP_ACCESS_RESTRICTED_SETTINGS, OPSTR_ACCESS_RESTRICTED_SETTINGS, - "ACCESS_RESTRICTED_SETTINGS").setDefaultMode( - android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() - ? MODE_DEFAULT : MODE_ALLOWED) + "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(AppOpsManager.MODE_ALLOWED) .setDisableReset(true).setRestrictRead(true).build(), new AppOpInfo.Builder(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO, "RECEIVE_SOUNDTRIGGER_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED) diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index bd80dc1e3c60..69b5222aa314 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -499,32 +499,31 @@ public class NotificationManager { /** * Activity Action: Launch an Automatic Zen Rule configuration screen - * <p> - * Input: Optionally, {@link #EXTRA_AUTOMATIC_RULE_ID}, if the configuration screen for an + * + * <p> Input: Optionally, {@link #EXTRA_AUTOMATIC_RULE_ID}, if the configuration screen for an * existing rule should be displayed. If the rule id is missing or null, apps should display * a configuration screen where users can create a new instance of the rule. - * <p> - * Output: Nothing - * <p> - * You can have multiple activities handling this intent, if you support multiple - * {@link AutomaticZenRule rules}. In order for the system to properly display all of your - * rule types so that users can create new instances or configure existing ones, you need - * to add some extra metadata ({@link #META_DATA_AUTOMATIC_RULE_TYPE}) - * to your activity tag in your manifest. If you'd like to limit the number of rules a user - * can create from this flow, you can additionally optionally include - * {@link #META_DATA_RULE_INSTANCE_LIMIT}. - * - * For example, - * <meta-data - * android:name="android.app.zen.automatic.ruleType" - * android:value="@string/my_condition_rule"> - * </meta-data> - * <meta-data - * android:name="android.app.zen.automatic.ruleInstanceLimit" - * android:value="1"> - * </meta-data> - * </p> - * </p> + * + * <p> Output: Nothing + * + * <p> You can have multiple activities handling this intent, if you support multiple + * {@link AutomaticZenRule rules}. In order for the system to properly display all of your + * rule types so that users can create new instances or configure existing ones, you need + * to add some extra metadata ({@link #META_DATA_AUTOMATIC_RULE_TYPE}) + * to your activity tag in your manifest. If you'd like to limit the number of rules a user + * can create from this flow, you can additionally optionally include + * {@link #META_DATA_RULE_INSTANCE_LIMIT}. For example, + * + * <pre> + * {@code + * <meta-data + * android:name="android.service.zen.automatic.ruleType" + * android:value="@string/my_condition_rule" /> + * <meta-data + * android:name="android.service.zen.automatic.ruleInstanceLimit" + * android:value="1" /> + * } + * </pre> * * @see #addAutomaticZenRule(AutomaticZenRule) */ diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl index fc7f85cceefc..39371a3dbd3f 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl @@ -32,7 +32,7 @@ oneway interface IVirtualDeviceActivityListener { * @param topActivity The component name of the top activity. * @param userId The user ID associated with the top activity. */ - void onTopActivityChanged(int displayId, in ComponentName topActivity, in int userId); + void onTopActivityChanged(int displayId, in ComponentName topActivity, int userId); /** * Called when the display becomes empty (e.g. if the user hits back on the last @@ -41,4 +41,13 @@ oneway interface IVirtualDeviceActivityListener { * @param displayId The display ID that became empty. */ void onDisplayEmpty(int displayId); + + /** + * Called when an activity launch was blocked due to a policy violation. + * + * @param displayId The display ID on which the activity tried to launch. + * @param componentName The component name of the blocked activity. + * @param userId The user ID associated with the blocked activity. + */ + void onActivityLaunchBlocked(int displayId, in ComponentName componentName, int userId); } diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java index 4cbcb68a9015..d3fcfc6f0ecc 100644 --- a/core/java/android/companion/virtual/VirtualDeviceInternal.java +++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java @@ -128,6 +128,22 @@ public class VirtualDeviceInternal { Binder.restoreCallingIdentity(token); } } + + @Override + public void onActivityLaunchBlocked(int displayId, ComponentName componentName, + @UserIdInt int userId) { + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mActivityListenersLock) { + for (int i = 0; i < mActivityListeners.size(); i++) { + mActivityListeners.valueAt(i) + .onActivityLaunchBlocked(displayId, componentName, userId); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } }; private final IVirtualDeviceSoundEffectListener mSoundEffectListener = new IVirtualDeviceSoundEffectListener.Stub() { @@ -525,6 +541,12 @@ public class VirtualDeviceInternal { public void onDisplayEmpty(int displayId) { mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId)); } + + public void onActivityLaunchBlocked(int displayId, ComponentName componentName, + @UserIdInt int userId) { + mExecutor.execute(() -> + mActivityListener.onActivityLaunchBlocked(displayId, componentName, userId)); + } } /** diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 88c3d3826b38..296ca33cdaec 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -1126,7 +1126,7 @@ public final class VirtualDeviceManager { } /** - * Listener for activity changes in this virtual device. + * Listener for activity changes and other activity events on a virtual device. * * @hide */ @@ -1167,6 +1167,20 @@ public final class VirtualDeviceManager { * @param displayId The display ID that became empty. */ void onDisplayEmpty(int displayId); + + /** + * Called when an activity launch was blocked due to a policy violation. + * + * @param displayId The display ID on which the activity tried to launch. + * @param componentName The component name of the blocked activity. + * @param userId The user ID associated with the blocked activity. + * + * @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY + * @see VirtualDevice#addActivityPolicyExemption(ComponentName) + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API) + default void onActivityLaunchBlocked(int displayId, @NonNull ComponentName componentName, + @UserIdInt int userId) {} } /** diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index f9a9da1ae448..f7f842f58511 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -171,7 +171,7 @@ public final class VirtualDeviceParams implements Parcelable { * @hide */ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, - POLICY_TYPE_CLIPBOARD}) + POLICY_TYPE_CLIPBOARD, POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR}) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) public @interface DynamicPolicyType {} @@ -263,6 +263,22 @@ public final class VirtualDeviceParams implements Parcelable { @FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA) public static final int POLICY_TYPE_CAMERA = 5; + /** + * Tells the virtual device framework how to handle activity launches that were blocked due to + * the current activity policy. + * + * <ul> + * <li>{@link #DEVICE_POLICY_DEFAULT}: Show UI informing the user of the blocked activity + * launch on the virtual display that the activity was originally launched on. + * <li>{@link #DEVICE_POLICY_CUSTOM}: Does not inform the user of the blocked activity + * launch. The virtual device owner can use this policy together with + * {@link VirtualDeviceManager.ActivityListener#onActivityLaunchBlocked} to provide custom + * experience on the virtual device. + * </ul> + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API) + public static final int POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR = 6; + private final int mLockState; @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts; @NavigationPolicy @@ -1174,6 +1190,10 @@ public final class VirtualDeviceParams implements Parcelable { mDevicePolicies.delete(POLICY_TYPE_CAMERA); } + if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) { + mDevicePolicies.delete(POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR); + } + if ((mAudioPlaybackSessionId != AUDIO_SESSION_ID_GENERATE || mAudioRecordingSessionId != AUDIO_SESSION_ID_GENERATE) && mDevicePolicies.get(POLICY_TYPE_AUDIO, DEVICE_POLICY_DEFAULT) diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index cd8082c1e27d..64d20817d050 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -41,6 +41,13 @@ flag { flag { namespace: "virtual_devices" + name: "activity_control_api" + description: "Enable APIs for fine grained activity policy, fallback and callbacks" + bug: "333443509" +} + +flag { + namespace: "virtual_devices" name: "camera_device_awareness" description: "Enable device awareness in camera service" bug: "305170199" diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index a9c07d174767..6f5bb4ad724f 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -126,7 +126,7 @@ flag { description: "Talkback focus doesn't move to the 'If you change your Google Account picture…' after swiping next to move the focus from 'Choose a picture'" bug: "330835921" metadata { - purpose: PURPOSE_BUGFIX + purpose: PURPOSE_BUGFIX } } @@ -136,7 +136,7 @@ flag { description: "Talkback doesn't announce 'selected' after double tapping the button in the picture list in 'Choose a picture' page." bug: "330840549" metadata { - purpose: PURPOSE_BUGFIX + purpose: PURPOSE_BUGFIX } } @@ -146,10 +146,21 @@ flag { description: "Fix potential unexpected behavior due to concurrent file writing" bug: "339351031" metadata { - purpose: PURPOSE_BUGFIX + purpose: PURPOSE_BUGFIX } } +flag { + name: "cache_user_serial_number" + namespace: "multiuser" + description: "Optimise user serial number retrieval" + bug: "340018451" + metadata { + purpose: PURPOSE_BUGFIX + } +} + + # This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile. flag { name: "enable_private_space_features" diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index 5031faa81afa..7b181176ae25 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -32,6 +32,9 @@ import android.util.MathUtils; import android.util.SparseArray; import android.util.StateSet; import android.util.Xml; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; +import android.util.proto.ProtoUtils; import com.android.internal.R; import com.android.internal.graphics.ColorUtils; @@ -44,7 +47,9 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * @@ -793,4 +798,61 @@ public class ColorStateList extends ComplexColor implements Parcelable { return new ColorStateList(stateSpecs, colors); } }; + + /** @hide */ + public void writeToProto(ProtoOutputStream out) { + for (int[] states : mStateSpecs) { + long specToken = out.start(ColorStateListProto.STATE_SPECS); + for (int state : states) { + out.write(ColorStateListProto.StateSpec.STATE, state); + } + out.end(specToken); + } + for (int color : mColors) { + out.write(ColorStateListProto.COLORS, color); + } + } + + /** @hide */ + public static ColorStateList createFromProto(ProtoInputStream in) + throws Exception { + List<int[]> stateSpecs = new ArrayList<>(); + List<Integer> colors = new ArrayList<>(); + + + while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) ColorStateListProto.COLORS: + colors.add(in.readInt(ColorStateListProto.COLORS)); + break; + case (int) ColorStateListProto.STATE_SPECS: + final long stateToken = in.start(ColorStateListProto.STATE_SPECS); + List<Integer> states = new ArrayList<>(); + while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) ColorStateListProto.StateSpec.STATE: + states.add(in.readInt(ColorStateListProto.StateSpec.STATE)); + break; + default: + Log.w(TAG, "Unhandled field while reading Icon proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + int[] statesArray = new int[states.size()]; + Arrays.setAll(statesArray, states::get); + stateSpecs.add(statesArray); + in.end(stateToken); + break; + default: + Log.w(TAG, "Unhandled field while reading Icon proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + + int[][] stateSpecsArray = new int[stateSpecs.size()][]; + Arrays.setAll(stateSpecsArray, stateSpecs::get); + int[] colorsArray = new int[colors.size()]; + Arrays.setAll(colorsArray, colors::get); + return new ColorStateList(stateSpecsArray, colorsArray); + } } diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index c2424e8a6529..cbac912e33e8 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -32,6 +32,7 @@ import android.app.ActivityThread; import android.app.AppOpsManager; import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.UnsupportedAppUsage; +import android.content.AttributionSource.ScopedParcelState; import android.content.Context; import android.graphics.ImageFormat; import android.graphics.Point; @@ -45,6 +46,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -293,10 +295,14 @@ public class Camera { @SuppressLint("UnflaggedApi") // @TestApi without associated feature. @TestApi public static int getNumberOfCameras(@NonNull Context context) { - return _getNumberOfCameras(context.getDeviceId(), getDevicePolicyFromContext(context)); + try (ScopedParcelState clientAttribution = + context.getAttributionSource().asScopedParcelState()) { + return _getNumberOfCameras( + clientAttribution.getParcel(), getDevicePolicyFromContext(context)); + } } - private static native int _getNumberOfCameras(int deviceId, int devicePolicy); + private static native int _getNumberOfCameras(Parcel clientAttributionParcel, int devicePolicy); /** * Returns the information about a particular camera. @@ -321,8 +327,16 @@ public class Camera { @TestApi public static void getCameraInfo(int cameraId, @NonNull Context context, int rotationOverride, CameraInfo cameraInfo) { - _getCameraInfo(cameraId, rotationOverride, context.getDeviceId(), - getDevicePolicyFromContext(context), cameraInfo); + try (ScopedParcelState clientAttribution = + context.getAttributionSource().asScopedParcelState()) { + _getCameraInfo( + cameraId, + rotationOverride, + clientAttribution.getParcel(), + getDevicePolicyFromContext(context), + cameraInfo); + } + IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); IAudioService audioService = IAudioService.Stub.asInterface(b); try { @@ -336,8 +350,12 @@ public class Camera { } } - private native static void _getCameraInfo(int cameraId, int rotationOverride, - int deviceId, int devicePolicy, CameraInfo cameraInfo); + private native static void _getCameraInfo( + int cameraId, + int rotationOverride, + Parcel clientAttributionParcel, + int devicePolicy, + CameraInfo cameraInfo); private static int getDevicePolicyFromContext(Context context) { if (context.getDeviceId() == DEVICE_ID_DEFAULT @@ -545,9 +563,18 @@ public class Camera { } boolean forceSlowJpegMode = shouldForceSlowJpegMode(); - return native_setup(new WeakReference<>(this), cameraId, - ActivityThread.currentOpPackageName(), rotationOverride, forceSlowJpegMode, - context.getDeviceId(), getDevicePolicyFromContext(context)); + + try (ScopedParcelState clientAttribution = + context.getAttributionSource().asScopedParcelState()) { + return native_setup( + new WeakReference<>(this), + cameraId, + ActivityThread.currentOpPackageName(), + rotationOverride, + forceSlowJpegMode, + clientAttribution.getParcel(), + getDevicePolicyFromContext(context)); + } } private boolean shouldForceSlowJpegMode() { @@ -630,8 +657,14 @@ public class Camera { } @UnsupportedAppUsage - private native int native_setup(Object cameraThis, int cameraId, String packageName, - int rotationOverride, boolean forceSlowJpegMode, int deviceId, int devicePolicy); + private native int native_setup( + Object cameraThis, + int cameraId, + String packageName, + int rotationOverride, + boolean forceSlowJpegMode, + Parcel clientAttributionParcel, + int devicePolicy); private native final void native_release(); @@ -2267,9 +2300,11 @@ public class Camera { private static final String KEY_MIN_EXPOSURE_COMPENSATION = "min-exposure-compensation"; private static final String KEY_EXPOSURE_COMPENSATION_STEP = "exposure-compensation-step"; private static final String KEY_AUTO_EXPOSURE_LOCK = "auto-exposure-lock"; - private static final String KEY_AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported"; + private static final String KEY_AUTO_EXPOSURE_LOCK_SUPPORTED = + "auto-exposure-lock-supported"; private static final String KEY_AUTO_WHITEBALANCE_LOCK = "auto-whitebalance-lock"; - private static final String KEY_AUTO_WHITEBALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported"; + private static final String KEY_AUTO_WHITEBALANCE_LOCK_SUPPORTED = + "auto-whitebalance-lock-supported"; private static final String KEY_METERING_AREAS = "metering-areas"; private static final String KEY_MAX_NUM_METERING_AREAS = "max-num-metering-areas"; private static final String KEY_ZOOM = "zoom"; @@ -2286,7 +2321,8 @@ public class Camera { private static final String KEY_RECORDING_HINT = "recording-hint"; private static final String KEY_VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported"; private static final String KEY_VIDEO_STABILIZATION = "video-stabilization"; - private static final String KEY_VIDEO_STABILIZATION_SUPPORTED = "video-stabilization-supported"; + private static final String KEY_VIDEO_STABILIZATION_SUPPORTED = + "video-stabilization-supported"; // Parameter key suffix for supported values. private static final String SUPPORTED_VALUES_SUFFIX = "-values"; diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 9eb97452444f..2dbd4b81fe6c 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -35,6 +35,7 @@ import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.compat.annotation.Overridable; +import android.content.AttributionSourceState; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Point; @@ -106,6 +107,7 @@ public final class CameraManager { private final boolean DEBUG = false; private static final int USE_CALLING_UID = -1; + private static final int USE_CALLING_PID = -1; @SuppressWarnings("unused") private static final int API_VERSION_1 = 1; @@ -418,9 +420,12 @@ public final class CameraManager { public boolean isConcurrentSessionConfigurationSupported( @NonNull Map<String, SessionConfiguration> cameraIdAndSessionConfig) throws CameraAccessException { - return CameraManagerGlobal.get().isConcurrentSessionConfigurationSupported( - cameraIdAndSessionConfig, mContext.getApplicationInfo().targetSdkVersion, - mContext.getDeviceId(), getDevicePolicyFromContext(mContext)); + return CameraManagerGlobal.get() + .isConcurrentSessionConfigurationSupported( + cameraIdAndSessionConfig, + mContext.getApplicationInfo().targetSdkVersion, + getClientAttribution(), + getDevicePolicyFromContext(mContext)); } /** @@ -660,11 +665,14 @@ public final class CameraManager { } try { for (String physicalCameraId : physicalCameraIds) { + AttributionSourceState clientAttribution = getClientAttribution(); + clientAttribution.deviceId = DEVICE_ID_DEFAULT; CameraMetadataNative physicalCameraInfo = - cameraService.getCameraCharacteristics(physicalCameraId, + cameraService.getCameraCharacteristics( + physicalCameraId, mContext.getApplicationInfo().targetSdkVersion, /*rotationOverride*/ ICameraService.ROTATION_OVERRIDE_NONE, - DEVICE_ID_DEFAULT, + clientAttribution, DEVICE_POLICY_DEFAULT); StreamConfiguration[] configs = physicalCameraInfo.get( CameraCharacteristics. @@ -756,9 +764,13 @@ public final class CameraManager { "Camera service is currently unavailable"); } try { - CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId, - mContext.getApplicationInfo().targetSdkVersion, rotationOverride, - mContext.getDeviceId(), getDevicePolicyFromContext(mContext)); + CameraMetadataNative info = + cameraService.getCameraCharacteristics( + cameraId, + mContext.getApplicationInfo().targetSdkVersion, + rotationOverride, + getClientAttribution(), + getDevicePolicyFromContext(mContext)); characteristics = prepareCameraCharacteristics(cameraId, info, cameraService); } catch (ServiceSpecificException e) { throw ExceptionUtils.throwAsPublicException(e); @@ -951,15 +963,33 @@ public final class CameraManager { } /** + * Constructs an AttributionSourceState with only the uid, pid, and deviceId fields set + * + * <p>This method is a temporary stopgap in the transition to using AttributionSource. Currently + * AttributionSourceState is only used as a vehicle for passing deviceId, uid, and pid + * arguments.</p> + * + * @hide + */ + public AttributionSourceState getClientAttribution() { + // TODO: Send the full contextAttribution over aidl, remove USE_CALLING_* + AttributionSourceState contextAttribution = + mContext.getAttributionSource().asState(); + AttributionSourceState clientAttribution = + new AttributionSourceState(); + clientAttribution.uid = USE_CALLING_UID; + clientAttribution.pid = USE_CALLING_PID; + clientAttribution.deviceId = contextAttribution.deviceId; + clientAttribution.next = new AttributionSourceState[0]; + return clientAttribution; + } + + /** * Helper for opening a connection to a camera with the given ID. * * @param cameraId The unique identifier of the camera device to open * @param callback The callback for the camera. Must not be null. * @param executor The executor to invoke the callback with. Must not be null. - * @param uid The UID of the application actually opening the camera. - * Must be USE_CALLING_UID unless the caller is a service - * that is trusted to open the device on behalf of an - * application and to forward the real UID. * * @throws CameraAccessException if the camera is disabled by device policy, * too many camera devices are already open, or the cameraId does not match @@ -974,7 +1004,7 @@ public final class CameraManager { * @see android.app.admin.DevicePolicyManager#setCameraDisabled */ private CameraDevice openCameraDeviceUserAsync(String cameraId, - CameraDevice.StateCallback callback, Executor executor, final int uid, + CameraDevice.StateCallback callback, Executor executor, final int oomScoreOffset, int rotationOverride) throws CameraAccessException { CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); CameraDevice device = null; @@ -1005,11 +1035,19 @@ public final class CameraManager { "Camera service is currently unavailable"); } - cameraUser = cameraService.connectDevice(callbacks, cameraId, - mContext.getOpPackageName(), mContext.getAttributionTag(), uid, - oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion, - rotationOverride, mContext.getDeviceId(), - getDevicePolicyFromContext(mContext)); + AttributionSourceState clientAttribution = + getClientAttribution(); + cameraUser = + cameraService.connectDevice( + callbacks, + cameraId, + mContext.getOpPackageName(), + mContext.getAttributionTag(), + oomScoreOffset, + mContext.getApplicationInfo().targetSdkVersion, + rotationOverride, + clientAttribution, + getDevicePolicyFromContext(mContext)); } catch (ServiceSpecificException e) { if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) { throw new AssertionError("Should've gone down the shim path"); @@ -1135,8 +1173,8 @@ public final class CameraManager { public void openCamera(@NonNull String cameraId, @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler) throws CameraAccessException { - openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler), - USE_CALLING_UID); + + openCameraImpl(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler)); } /** @@ -1172,8 +1210,8 @@ public final class CameraManager { public void openCamera(@NonNull String cameraId, boolean overrideToPortrait, @Nullable Handler handler, @NonNull final CameraDevice.StateCallback callback) throws CameraAccessException { - openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler), - USE_CALLING_UID, /*oomScoreOffset*/0, + openCameraImpl(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler), + /*oomScoreOffset*/0, overrideToPortrait ? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT : ICameraService.ROTATION_OVERRIDE_NONE); @@ -1221,7 +1259,7 @@ public final class CameraManager { if (executor == null) { throw new IllegalArgumentException("executor was null"); } - openCameraForUid(cameraId, callback, executor, USE_CALLING_UID); + openCameraImpl(cameraId, callback, executor); } /** @@ -1289,13 +1327,13 @@ public final class CameraManager { throw new IllegalArgumentException( "oomScoreOffset < 0, cannot increase priority of camera client"); } - openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset, + openCameraImpl(cameraId, callback, executor, oomScoreOffset, getRotationOverride(mContext)); } /** - * Open a connection to a camera with the given ID, on behalf of another application - * specified by clientUid. Also specify the minimum oom score and process state the application + * Open a connection to a camera with the given ID, on behalf of another application. + * Also specify the minimum oom score and process state the application * should have, as seen by the cameraserver. * * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows @@ -1303,9 +1341,6 @@ public final class CameraManager { * done by services trusted by the camera subsystem to act on behalf of applications and * to forward the real UID.</p> * - * @param clientUid - * The UID of the application on whose behalf the camera is being opened. - * Must be USE_CALLING_UID unless the caller is a trusted service. * @param oomScoreOffset * The minimum oom score that cameraservice must see for this client. * @param rotationOverride @@ -1313,9 +1348,9 @@ public final class CameraManager { * that should be followed for this camera id connection * @hide */ - public void openCameraForUid(@NonNull String cameraId, + public void openCameraImpl(@NonNull String cameraId, @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor, - int clientUid, int oomScoreOffset, int rotationOverride) + int oomScoreOffset, int rotationOverride) throws CameraAccessException { if (cameraId == null) { @@ -1327,29 +1362,24 @@ public final class CameraManager { throw new IllegalArgumentException("No cameras available on device"); } - openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset, + openCameraDeviceUserAsync(cameraId, callback, executor, oomScoreOffset, rotationOverride); } /** - * Open a connection to a camera with the given ID, on behalf of another application - * specified by clientUid. + * Open a connection to a camera with the given ID, on behalf of another application. * * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows * the caller to specify the UID to use for permission/etc verification. This can only be * done by services trusted by the camera subsystem to act on behalf of applications and * to forward the real UID.</p> * - * @param clientUid - * The UID of the application on whose behalf the camera is being opened. - * Must be USE_CALLING_UID unless the caller is a trusted service. - * * @hide */ - public void openCameraForUid(@NonNull String cameraId, - @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor, - int clientUid) throws CameraAccessException { - openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0, + public void openCameraImpl(@NonNull String cameraId, + @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor) + throws CameraAccessException { + openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0, getRotationOverride(mContext)); } @@ -1397,8 +1427,12 @@ public final class CameraManager { if (CameraManagerGlobal.sCameraServiceDisabled) { throw new IllegalArgumentException("No cameras available on device"); } - CameraManagerGlobal.get().setTorchMode(cameraId, enabled, mContext.getDeviceId(), - getDevicePolicyFromContext(mContext)); + CameraManagerGlobal.get() + .setTorchMode( + cameraId, + enabled, + getClientAttribution(), + getDevicePolicyFromContext(mContext)); } /** @@ -1461,8 +1495,12 @@ public final class CameraManager { if (CameraManagerGlobal.sCameraServiceDisabled) { throw new IllegalArgumentException("No camera available on device"); } - CameraManagerGlobal.get().turnOnTorchWithStrengthLevel(cameraId, torchStrength, - mContext.getDeviceId(), getDevicePolicyFromContext(mContext)); + CameraManagerGlobal.get() + .turnOnTorchWithStrengthLevel( + cameraId, + torchStrength, + getClientAttribution(), + getDevicePolicyFromContext(mContext)); } /** @@ -1488,8 +1526,11 @@ public final class CameraManager { if (CameraManagerGlobal.sCameraServiceDisabled) { throw new IllegalArgumentException("No camera available on device."); } - return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId, mContext.getDeviceId(), - getDevicePolicyFromContext(mContext)); + return CameraManagerGlobal.get() + .getTorchStrengthLevel( + cameraId, + getClientAttribution(), + getDevicePolicyFromContext(mContext)); } /** @@ -2499,7 +2540,9 @@ public final class CameraManager { public boolean isConcurrentSessionConfigurationSupported( @NonNull Map<String, SessionConfiguration> cameraIdsAndSessionConfigurations, - int targetSdkVersion, int deviceId, int devicePolicy) + int targetSdkVersion, + AttributionSourceState clientAttribution, + int devicePolicy) throws CameraAccessException { if (cameraIdsAndSessionConfigurations == null) { throw new IllegalArgumentException("cameraIdsAndSessionConfigurations was null"); @@ -2517,9 +2560,12 @@ public final class CameraManager { for (Set<DeviceCameraInfo> combination : mConcurrentCameraIdCombinations) { Set<DeviceCameraInfo> infos = new ArraySet<>(); for (String cameraId : cameraIdsAndSessionConfigurations.keySet()) { - infos.add(new DeviceCameraInfo(cameraId, - devicePolicy == DEVICE_POLICY_DEFAULT - ? DEVICE_ID_DEFAULT : deviceId)); + infos.add( + new DeviceCameraInfo( + cameraId, + devicePolicy == DEVICE_POLICY_DEFAULT + ? DEVICE_ID_DEFAULT + : clientAttribution.deviceId)); } if (combination.containsAll(infos)) { subsetFound = true; @@ -2541,7 +2587,7 @@ public final class CameraManager { } try { return mCameraService.isConcurrentSessionConfigurationSupported( - cameraIdsAndConfigs, targetSdkVersion, deviceId, devicePolicy); + cameraIdsAndConfigs, targetSdkVersion, clientAttribution, devicePolicy); } catch (ServiceSpecificException e) { throw ExceptionUtils.throwAsPublicException(e); } catch (RemoteException e) { @@ -2580,7 +2626,11 @@ public final class CameraManager { return false; } - public void setTorchMode(String cameraId, boolean enabled, int deviceId, int devicePolicy) + public void setTorchMode( + String cameraId, + boolean enabled, + AttributionSourceState clientAttribution, + int devicePolicy) throws CameraAccessException { synchronized (mLock) { if (cameraId == null) { @@ -2594,8 +2644,8 @@ public final class CameraManager { } try { - cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder, deviceId, - devicePolicy); + cameraService.setTorchMode( + cameraId, enabled, mTorchClientBinder, clientAttribution, devicePolicy); } catch(ServiceSpecificException e) { throw ExceptionUtils.throwAsPublicException(e); } catch (RemoteException e) { @@ -2605,7 +2655,10 @@ public final class CameraManager { } } - public void turnOnTorchWithStrengthLevel(String cameraId, int torchStrength, int deviceId, + public void turnOnTorchWithStrengthLevel( + String cameraId, + int torchStrength, + AttributionSourceState clientAttribution, int devicePolicy) throws CameraAccessException { synchronized (mLock) { @@ -2620,8 +2673,12 @@ public final class CameraManager { } try { - cameraService.turnOnTorchWithStrengthLevel(cameraId, torchStrength, - mTorchClientBinder, deviceId, devicePolicy); + cameraService.turnOnTorchWithStrengthLevel( + cameraId, + torchStrength, + mTorchClientBinder, + clientAttribution, + devicePolicy); } catch(ServiceSpecificException e) { throw ExceptionUtils.throwAsPublicException(e); } catch (RemoteException e) { @@ -2631,7 +2688,8 @@ public final class CameraManager { } } - public int getTorchStrengthLevel(String cameraId, int deviceId, int devicePolicy) + public int getTorchStrengthLevel( + String cameraId, AttributionSourceState clientAttribution, int devicePolicy) throws CameraAccessException { int torchStrength; synchronized (mLock) { @@ -2646,8 +2704,9 @@ public final class CameraManager { } try { - torchStrength = cameraService.getTorchStrengthLevel(cameraId, deviceId, - devicePolicy); + torchStrength = + cameraService.getTorchStrengthLevel( + cameraId, clientAttribution, devicePolicy); } catch(ServiceSpecificException e) { throw ExceptionUtils.throwAsPublicException(e); } catch (RemoteException e) { diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java index df057a1489f0..4ddf602c447b 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java @@ -71,9 +71,12 @@ public class CameraDeviceSetupImpl extends CameraDevice.CameraDeviceSetup { } try { - CameraMetadataNative defaultRequest = cameraService.createDefaultRequest(mCameraId, - templateType, mContext.getDeviceId(), - mCameraManager.getDevicePolicyFromContext(mContext)); + CameraMetadataNative defaultRequest = + cameraService.createDefaultRequest( + mCameraId, + templateType, + mCameraManager.getClientAttribution(), + mCameraManager.getDevicePolicyFromContext(mContext)); CameraDeviceImpl.disableZslIfNeeded(defaultRequest, mTargetSdkVersion, templateType); @@ -104,9 +107,11 @@ public class CameraDeviceSetupImpl extends CameraDevice.CameraDeviceSetup { } try { - return cameraService.isSessionConfigurationWithParametersSupported(mCameraId, - mTargetSdkVersion, config, - mContext.getDeviceId(), + return cameraService.isSessionConfigurationWithParametersSupported( + mCameraId, + mTargetSdkVersion, + config, + mCameraManager.getClientAttribution(), mCameraManager.getDevicePolicyFromContext(mContext)); } catch (ServiceSpecificException e) { throw ExceptionUtils.throwAsPublicException(e); @@ -133,12 +138,14 @@ public class CameraDeviceSetupImpl extends CameraDevice.CameraDeviceSetup { } try { - CameraMetadataNative metadata = cameraService.getSessionCharacteristics( - mCameraId, mTargetSdkVersion, - CameraManager.getRotationOverride(mContext), - sessionConfig, - mContext.getDeviceId(), - mCameraManager.getDevicePolicyFromContext(mContext)); + CameraMetadataNative metadata = + cameraService.getSessionCharacteristics( + mCameraId, + mTargetSdkVersion, + CameraManager.getRotationOverride(mContext), + sessionConfig, + mCameraManager.getClientAttribution(), + mCameraManager.getDevicePolicyFromContext(mContext)); return mCameraManager.prepareCameraCharacteristics(mCameraId, metadata, cameraService); diff --git a/core/java/android/hardware/devicestate/feature/flags.aconfig b/core/java/android/hardware/devicestate/feature/flags.aconfig index 12d3f94ec982..a09c84dcedcb 100644 --- a/core/java/android/hardware/devicestate/feature/flags.aconfig +++ b/core/java/android/hardware/devicestate/feature/flags.aconfig @@ -8,4 +8,13 @@ flag { description: "Updated DeviceState hasProperty API" bug: "293636629" is_fixed_read_only: true +} + +flag { + name: "device_state_property_migration" + is_exported: true + namespace: "windowing_sdk" + description: "Client migration to updated DeviceStateManager API's" + bug: "336640888" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java index b8f2c008d8b2..3be911abe732 100644 --- a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java +++ b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java @@ -69,8 +69,13 @@ public final class VirtualRotaryEncoderScrollEvent implements Parcelable { } /** - * Returns the scroll amount, normalized from -1.0 to 1.0, inclusive. Positive values - * indicate scrolling forward (e.g. down in a vertical list); negative values, backward. + * Returns the scroll amount, normalized from -1.0 to 1.0, inclusive. + * <p> + * Positive values indicate scrolling forward (e.g. down in a vertical list); negative values, + * backward. + * <p> + * Values of 1.0 or -1.0 represent the maximum supported scroll. + * </p> */ public @FloatRange(from = -1.0f, to = 1.0f) float getScrollAmount() { return mScrollAmount; @@ -91,7 +96,7 @@ public final class VirtualRotaryEncoderScrollEvent implements Parcelable { */ public static final class Builder { - private float mScrollAmount; + @FloatRange(from = -1.0f, to = 1.0f) private float mScrollAmount = 0.0f; private long mEventTimeNanos = 0L; /** @@ -102,9 +107,13 @@ public final class VirtualRotaryEncoderScrollEvent implements Parcelable { } /** - * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive. Positive values - * indicate scrolling forward (e.g. down in a vertical list); negative values, backward. - * + * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive. + * <p> + * Positive values indicate scrolling forward (e.g. down in a vertical list); negative + * values, backward. + * <p> + * Values of 1.0 or -1.0 represent the maximum supported scroll. + * </p> * @return this builder, to allow for chaining of calls */ public @NonNull Builder setScrollAmount( diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index 90d82e7ad2a4..61cc23d994f3 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -870,6 +870,16 @@ public final class BatteryUsageStats implements Parcelable, Closeable { } /** + * Returns true if this Builder is configured to hold data for the specified + * custom power component ID. + */ + public boolean isSupportedCustomPowerComponent(int componentId) { + return componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + && componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + + mBatteryConsumerDataLayout.customPowerComponentCount; + } + + /** * Constructs a read-only object using the Builder values. */ @NonNull diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java new file mode 100644 index 000000000000..72b5cf79133d --- /dev/null +++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java @@ -0,0 +1,1648 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Handler; +import android.os.Trace; +import android.util.Log; +import android.util.Printer; +import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; + +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Low-level class holding the list of messages to be dispatched by a + * {@link Looper}. Messages are not added directly to a MessageQueue, + * but rather through {@link Handler} objects associated with the Looper. + * + * <p>You can retrieve the MessageQueue for the current thread with + * {@link Looper#myQueue() Looper.myQueue()}. + */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass +@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( + "com.android.platform.test.ravenwood.nativesubstitution.MessageQueue_host") +public final class MessageQueue { + private static final String TAG = "ConcurrentMessageQueue"; + private static final boolean DEBUG = false; + private static final boolean TRACE = false; + + // True if the message queue can be quit. + private final boolean mQuitAllowed; + + @SuppressWarnings("unused") + private long mPtr; // used by native code + + @IntDef(value = { + STACK_NODE_MESSAGE, + STACK_NODE_ACTIVE, + STACK_NODE_PARKED, + STACK_NODE_TIMEDPARK}) + @Retention(RetentionPolicy.SOURCE) + private @interface StackNodeType {} + + /* + * Stack node types. STACK_NODE_MESSAGE indicates a node containing a message. + * The other types indicate what state our Looper thread is in. The bottom of + * the stack is always a single state node. Message nodes are added on top. + */ + private static final int STACK_NODE_MESSAGE = 0; + /* + * Active state indicates that next() is processing messages + */ + private static final int STACK_NODE_ACTIVE = 1; + /* + * Parked state indicates that the Looper thread is sleeping indefinitely (nothing to deliver) + */ + private static final int STACK_NODE_PARKED = 2; + /* + * Timed Park state indicates that the Looper thread is sleeping, waiting for a message + * deadline + */ + private static final int STACK_NODE_TIMEDPARK = 3; + + /* Describes a node in the Treiber stack */ + static class StackNode { + @StackNodeType + private final int mType; + + StackNode(@StackNodeType int type) { + mType = type; + } + + @StackNodeType + final int getNodeType() { + return mType; + } + + final boolean isMessageNode() { + return mType == STACK_NODE_MESSAGE; + } + } + + static final class MessageNode extends StackNode implements Comparable<MessageNode> { + private final Message mMessage; + volatile StackNode mNext; + StateNode mBottomOfStack; + boolean mWokeUp; + final long mInsertSeq; + private static final VarHandle sRemovedFromStack; + private volatile boolean mRemovedFromStackValue; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + sRemovedFromStack = l.findVarHandle(MessageQueue.MessageNode.class, + "mRemovedFromStackValue", boolean.class); + } catch (Exception e) { + Log.wtf(TAG, "VarHandle lookup failed with exception: " + e); + throw new ExceptionInInitializerError(e); + } + } + + MessageNode(@NonNull Message message, long insertSeq) { + super(STACK_NODE_MESSAGE); + mMessage = message; + mInsertSeq = insertSeq; + } + + long getWhen() { + return mMessage.when; + } + + boolean isRemovedFromStack() { + return mRemovedFromStackValue; + } + + boolean removeFromStack() { + return sRemovedFromStack.compareAndSet(this, false, true); + } + + boolean isAsync() { + return mMessage.isAsynchronous(); + } + + boolean isBarrier() { + return mMessage.target == null; + } + + @Override + public int compareTo(@NonNull MessageNode messageNode) { + Message other = messageNode.mMessage; + + int compared = Long.compare(mMessage.when, other.when); + if (compared == 0) { + compared = Long.compare(mInsertSeq, messageNode.mInsertSeq); + } + return compared; + } + } + + static class StateNode extends StackNode { + StateNode(int type) { + super(type); + } + } + + static final class TimedParkStateNode extends StateNode { + long mWhenToWake; + + TimedParkStateNode() { + super(STACK_NODE_TIMEDPARK); + } + } + + private static final StateNode sStackStateActive = new StateNode(STACK_NODE_ACTIVE); + private static final StateNode sStackStateParked = new StateNode(STACK_NODE_PARKED); + private final TimedParkStateNode mStackStateTimedPark = new TimedParkStateNode(); + + /* This is the top of our treiber stack. */ + private static final VarHandle sState; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + sState = l.findVarHandle(MessageQueue.class, "mStateValue", + MessageQueue.StackNode.class); + } catch (Exception e) { + Log.wtf(TAG, "VarHandle lookup failed with exception: " + e); + throw new ExceptionInInitializerError(e); + } + } + + private volatile StackNode mStateValue = sStackStateParked; + private final ConcurrentSkipListSet<MessageNode> mPriorityQueue = + new ConcurrentSkipListSet<MessageNode>(); + private final ConcurrentSkipListSet<MessageNode> mAsyncPriorityQueue = + new ConcurrentSkipListSet<MessageNode>(); + + /* + * This helps us ensure that messages with the same timestamp are inserted in FIFO order. + * Increments on each insert, starting at 0. MessageNode.compareTo() will compare sequences + * when delivery timestamps are identical. + */ + private static final VarHandle sNextInsertSeq; + private volatile long mNextInsertSeqValue = 0; + /* + * The exception to the FIFO order rule is sendMessageAtFrontOfQueue(). + * Those messages must be in LIFO order - SIGH. + * Decrements on each front of queue insert. + */ + private static final VarHandle sNextFrontInsertSeq; + private volatile long mNextFrontInsertSeqValue = -1; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + sNextInsertSeq = l.findVarHandle(MessageQueue.class, "mNextInsertSeqValue", + long.class); + sNextFrontInsertSeq = l.findVarHandle(MessageQueue.class, "mNextFrontInsertSeqValue", + long.class); + } catch (Exception e) { + Log.wtf(TAG, "VarHandle lookup failed with exception: " + e); + throw new ExceptionInInitializerError(e); + } + + } + + /* + * Tracks the number of queued and cancelled messages in our stack. + * + * On item cancellation, determine whether to wake next() to flush tombstoned messages. + * We track queued and cancelled counts as two ints packed into a single long. + */ + private static final class MessageCounts { + private static VarHandle sCounts; + private volatile long mCountsValue = 0; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + sCounts = l.findVarHandle(MessageQueue.MessageCounts.class, "mCountsValue", + long.class); + } catch (Exception e) { + Log.wtf(TAG, "VarHandle lookup failed with exception: " + e); + throw new ExceptionInInitializerError(e); + } + } + + /* We use a special value to indicate when next() has been woken for flush. */ + private static final long AWAKE = Long.MAX_VALUE; + /* + * Minimum number of messages in the stack which we need before we consider flushing + * tombstoned items. + */ + private static final int MESSAGE_FLUSH_THRESHOLD = 10; + + private static int numQueued(long val) { + return (int) (val >>> Integer.SIZE); + } + + private static int numCancelled(long val) { + return (int) val; + } + + private static long combineCounts(int queued, int cancelled) { + return ((long) queued << Integer.SIZE) | (long) cancelled; + } + + public void incrementQueued() { + while (true) { + long oldVal = mCountsValue; + int queued = numQueued(oldVal); + int cancelled = numCancelled(oldVal); + /* Use Math.max() to avoid overflow of queued count */ + long newVal = combineCounts(Math.max(queued + 1, queued), cancelled); + + /* Don't overwrite 'AWAKE' state */ + if (oldVal == AWAKE || sCounts.compareAndSet(this, oldVal, newVal)) { + break; + } + } + } + + public boolean incrementCancelled() { + while (true) { + long oldVal = mCountsValue; + if (oldVal == AWAKE) { + return false; + } + int queued = numQueued(oldVal); + int cancelled = numCancelled(oldVal); + boolean needsPurge = queued > MESSAGE_FLUSH_THRESHOLD + && (queued >> 1) < cancelled; + long newVal; + if (needsPurge) { + newVal = AWAKE; + } else { + newVal = combineCounts(queued, + Math.max(cancelled + 1, cancelled)); + } + + if (sCounts.compareAndSet(this, oldVal, newVal)) { + return needsPurge; + } + } + } + + public void clearCounts() { + mCountsValue = 0; + } + } + + private final MessageCounts mMessageCounts = new MessageCounts(); + + private final Object mIdleHandlersLock = new Object(); + @GuardedBy("mIdleHandlersLock") + private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); + private IdleHandler[] mPendingIdleHandlers; + + private final Object mFileDescriptorRecordsLock = new Object(); + @GuardedBy("mFileDescriptorRecordsLock") + private SparseArray<FileDescriptorRecord> mFileDescriptorRecords; + + private static final VarHandle sQuitting; + private boolean mQuittingValue = false; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + sQuitting = l.findVarHandle(MessageQueue.class, "mQuittingValue", boolean.class); + } catch (Exception e) { + Log.wtf(TAG, "VarHandle lookup failed with exception: " + e); + throw new ExceptionInInitializerError(e); + } + } + + // The next barrier token. + // Barriers are indicated by messages with a null target whose arg1 field carries the token. + private final AtomicInteger mNextBarrierToken = new AtomicInteger(1); + + private static native long nativeInit(); + private static native void nativeDestroy(long ptr); + private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/ + private static native void nativeWake(long ptr); + private static native boolean nativeIsPolling(long ptr); + private static native void nativeSetFileDescriptorEvents(long ptr, int fd, int events); + + MessageQueue(boolean quitAllowed) { + mQuitAllowed = quitAllowed; + mPtr = nativeInit(); + } + + @Override + protected void finalize() throws Throwable { + try { + dispose(); + } finally { + super.finalize(); + } + } + + // Disposes of the underlying message queue. + // Must only be called on the looper thread or the finalizer. + private void dispose() { + if (mPtr != 0) { + nativeDestroy(mPtr); + mPtr = 0; + } + } + + /** + * Returns true if the looper has no pending messages which are due to be processed. + * + * <p>This method is safe to call from any thread. + * + * @return True if the looper is idle. + */ + public boolean isIdle() { + MessageNode msgNode = null; + MessageNode asyncMsgNode = null; + + if (!mPriorityQueue.isEmpty()) { + try { + msgNode = mPriorityQueue.first(); + } catch (NoSuchElementException e) { } + } + + if (!mAsyncPriorityQueue.isEmpty()) { + try { + asyncMsgNode = mAsyncPriorityQueue.first(); + } catch (NoSuchElementException e) { } + } + + final long now = SystemClock.uptimeMillis(); + if ((msgNode != null && msgNode.getWhen() <= now) + || (asyncMsgNode != null && asyncMsgNode.getWhen() <= now)) { + return false; + } + + return true; + } + + /* Protects mNextIsDrainingStack */ + private final ReentrantLock mDrainingLock = new ReentrantLock(); + private boolean mNextIsDrainingStack = false; + private final Condition mDrainCompleted = mDrainingLock.newCondition(); + + /** + * Add a new {@link IdleHandler} to this message queue. This may be + * removed automatically for you by returning false from + * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is + * invoked, or explicitly removing it with {@link #removeIdleHandler}. + * + * <p>This method is safe to call from any thread. + * + * @param handler The IdleHandler to be added. + */ + public void addIdleHandler(@NonNull IdleHandler handler) { + if (handler == null) { + throw new NullPointerException("Can't add a null IdleHandler"); + } + synchronized (mIdleHandlersLock) { + mIdleHandlers.add(handler); + } + } + + /** + * Remove an {@link IdleHandler} from the queue that was previously added + * with {@link #addIdleHandler}. If the given object is not currently + * in the idle list, nothing is done. + * + * <p>This method is safe to call from any thread. + * + * @param handler The IdleHandler to be removed. + */ + public void removeIdleHandler(@NonNull IdleHandler handler) { + synchronized (mIdleHandlersLock) { + mIdleHandlers.remove(handler); + } + } + + /** + * Returns whether this looper's thread is currently polling for more work to do. + * This is a good signal that the loop is still alive rather than being stuck + * handling a callback. Note that this method is intrinsically racy, since the + * state of the loop can change before you get the result back. + * + * <p>This method is safe to call from any thread. + * + * @return True if the looper is currently polling for events. + * @hide + */ + public boolean isPolling() { + // If the loop is quitting then it must not be idling. + // We can assume mPtr != 0 when sQuitting is false. + return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr); + } + + /* Helper to choose the correct queue to insert into. */ + private void insertIntoPriorityQueue(MessageNode msgNode) { + if (msgNode.isAsync()) { + mAsyncPriorityQueue.add(msgNode); + } else { + mPriorityQueue.add(msgNode); + } + } + + private boolean removeFromPriorityQueue(MessageNode msgNode) { + if (msgNode.isAsync()) { + return mAsyncPriorityQueue.remove(msgNode); + } else { + return mPriorityQueue.remove(msgNode); + } + } + + private MessageNode pickEarliestNode(MessageNode nodeA, MessageNode nodeB) { + if (nodeA != null && nodeB != null) { + if (nodeA.compareTo(nodeB) < 0) { + return nodeA; + } + return nodeB; + } + + return nodeA != null ? nodeA : nodeB; + } + + private MessageNode iterateNext(Iterator<MessageNode> iter) { + if (iter.hasNext()) { + try { + return iter.next(); + } catch (NoSuchElementException e) { + /* The queue is empty - this can happen if we race with remove */ + } + } + return null; + } + + /* Move any non-cancelled messages into the priority queue */ + private void drainStack(StackNode oldTop) { + while (oldTop.isMessageNode()) { + MessageNode oldTopMessageNode = (MessageNode) oldTop; + if (oldTopMessageNode.removeFromStack()) { + insertIntoPriorityQueue(oldTopMessageNode); + } + MessageNode inserted = oldTopMessageNode; + oldTop = oldTopMessageNode.mNext; + /* + * removeMessages can walk this list while we are consuming it. + * Set our next pointer to null *after* we add the message to our + * priority queue. This way removeMessages() will always find the + * message, either in our list or in the priority queue. + */ + inserted.mNext = null; + } + } + + /* Set the stack state to Active, return a list of nodes to walk. */ + private StackNode swapAndSetStackStateActive() { + while (true) { + /* Set stack state to Active, get node list to walk later */ + StackNode current = (StackNode) sState.getVolatile(this); + if (current == sStackStateActive + || sState.compareAndSet(this, current, sStackStateActive)) { + return current; + } + } + } + + /* This is only read/written from the Looper thread */ + private int mNextPollTimeoutMillis; + private static final AtomicLong mMessagesDelivered = new AtomicLong(); + + private Message nextMessage() { + int i = 0; + + while (true) { + if (DEBUG) { + Log.d(TAG, "nextMessage loop #" + i); + i++; + } + + mDrainingLock.lock(); + mNextIsDrainingStack = true; + mDrainingLock.unlock(); + + /* + * Set our state to active, drain any items from the stack into our priority queues + */ + StackNode oldTop; + oldTop = swapAndSetStackStateActive(); + drainStack(oldTop); + + mDrainingLock.lock(); + mNextIsDrainingStack = false; + mDrainCompleted.signalAll(); + mDrainingLock.unlock(); + + /* + * The objective of this next block of code is to: + * - find a message to return (if any is ready) + * - find a next message we would like to return, after scheduling. + * - we make our scheduling decision based on this next message (if it exists). + * + * We have two queues to juggle and the presence of barriers throws an additional + * wrench into our plans. + * + * The last wrinkle is that remove() may delete items from underneath us. If we hit + * that case, we simply restart the loop. + */ + + /* Get the first node from each queue */ + Iterator<MessageNode> queueIter = mPriorityQueue.iterator(); + MessageNode msgNode = iterateNext(queueIter); + Iterator<MessageNode> asyncQueueIter = mAsyncPriorityQueue.iterator(); + MessageNode asyncMsgNode = iterateNext(asyncQueueIter); + + if (DEBUG) { + if (msgNode != null) { + Message msg = msgNode.mMessage; + Log.d(TAG, "Next found node what: " + msg.what + " when: " + msg.when + + " seq: " + msgNode.mInsertSeq + "barrier: " + + msgNode.isBarrier() + " now: " + SystemClock.uptimeMillis()); + } + if (asyncMsgNode != null) { + Message msg = asyncMsgNode.mMessage; + Log.d(TAG, "Next found async node what: " + msg.what + " when: " + msg.when + + " seq: " + asyncMsgNode.mInsertSeq + "barrier: " + + asyncMsgNode.isBarrier() + " now: " + + SystemClock.uptimeMillis()); + } + } + + /* + * the node which we will return, null if none are ready + */ + MessageNode found = null; + /* + * The node from which we will determine our next wakeup time. + * Null indicates there is no next message ready. If we found a node, + * we can leave this null as Looper will call us again after delivering + * the message. + */ + MessageNode next = null; + + long now = SystemClock.uptimeMillis(); + /* + * If we have a barrier we should return the async node (if it exists and is ready) + */ + if (msgNode != null && msgNode.isBarrier()) { + if (asyncMsgNode != null && now >= asyncMsgNode.getWhen()) { + found = asyncMsgNode; + } else { + next = asyncMsgNode; + } + } else { /* No barrier. */ + MessageNode earliest; + /* + * If we have two messages, pick the earliest option from either queue. + * Otherwise grab whichever node is non-null. If both are null we'll fall through. + */ + earliest = pickEarliestNode(msgNode, asyncMsgNode); + + if (earliest != null) { + if (now >= earliest.getWhen()) { + found = earliest; + } else { + next = earliest; + } + } + } + + if (DEBUG) { + if (found != null) { + Message msg = found.mMessage; + Log.d(TAG, "Will deliver node what: " + msg.what + " when: " + msg.when + + " seq: " + found.mInsertSeq + " barrier: " + found.isBarrier() + + " async: " + found.isAsync() + " now: " + + SystemClock.uptimeMillis()); + } else { + Log.d(TAG, "No node to deliver"); + } + if (next != null) { + Message msg = next.mMessage; + Log.d(TAG, "Next node what: " + msg.what + " when: " + msg.when + " seq: " + + next.mInsertSeq + " barrier: " + next.isBarrier() + " async: " + + next.isAsync() + + " now: " + SystemClock.uptimeMillis()); + } else { + Log.d(TAG, "No next node"); + } + } + + /* + * If we have a found message, we will get called again so there's no need to set state. + * In that case we can leave our state as ACTIVE. + * + * Otherwise we should determine how to park the thread. + */ + StateNode nextOp = sStackStateActive; + if (found == null) { + if (next == null) { + /* No message to deliver, sleep indefinitely */ + mNextPollTimeoutMillis = -1; + nextOp = sStackStateParked; + if (DEBUG) { + Log.d(TAG, "nextMessage next state is StackStateParked"); + } + } else { + /* Message not ready, or we found one to deliver already, set a timeout */ + long nextMessageWhen = next.getWhen(); + if (nextMessageWhen > now) { + mNextPollTimeoutMillis = (int) Math.min(nextMessageWhen - now, + Integer.MAX_VALUE); + } else { + mNextPollTimeoutMillis = 0; + } + + mStackStateTimedPark.mWhenToWake = now + mNextPollTimeoutMillis; + nextOp = mStackStateTimedPark; + if (DEBUG) { + Log.d(TAG, "nextMessage next state is StackStateTimedParked timeout ms " + + mNextPollTimeoutMillis + " mWhenToWake: " + + mStackStateTimedPark.mWhenToWake + " now " + now); + } + } + } + + /* + * Try to swap our state from Active back to Park or TimedPark. If we raced with + * enqueue, loop back around to pick up any new items. + */ + if (sState.compareAndSet(this, sStackStateActive, nextOp)) { + mMessageCounts.clearCounts(); + if (found != null) { + if (!removeFromPriorityQueue(found)) { + /* + * RemoveMessages() might be able to pull messages out from under us + * However we can detect that here and just loop around if it happens. + */ + continue; + } + + if (TRACE) { + Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet()); + } + return found.mMessage; + } + return null; + } + } + } + + Message next() { + final long ptr = mPtr; + if (ptr == 0) { + return null; + } + + mNextPollTimeoutMillis = 0; + int pendingIdleHandlerCount = -1; // -1 only during first iteration + while (true) { + if (mNextPollTimeoutMillis != 0) { + Binder.flushPendingCommands(); + } + + nativePollOnce(ptr, mNextPollTimeoutMillis); + + Message msg = nextMessage(); + if (msg != null) { + msg.markInUse(); + return msg; + } + + if ((boolean) sQuitting.getVolatile(this)) { + return null; + } + + synchronized (mIdleHandlersLock) { + // If first time idle, then get the number of idlers to run. + // Idle handles only run if the queue is empty or if the first message + // in the queue (possibly a barrier) is due to be handled in the future. + if (pendingIdleHandlerCount < 0 + && mNextPollTimeoutMillis != 0) { + pendingIdleHandlerCount = mIdleHandlers.size(); + } + if (pendingIdleHandlerCount <= 0) { + // No idle handlers to run. Loop and wait some more. + continue; + } + + if (mPendingIdleHandlers == null) { + mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; + } + mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); + } + + // Run the idle handlers. + // We only ever reach this code block during the first iteration. + for (int i = 0; i < pendingIdleHandlerCount; i++) { + final IdleHandler idler = mPendingIdleHandlers[i]; + mPendingIdleHandlers[i] = null; // release the reference to the handler + + boolean keep = false; + try { + keep = idler.queueIdle(); + } catch (Throwable t) { + Log.wtf(TAG, "IdleHandler threw exception", t); + } + + if (!keep) { + synchronized (mIdleHandlersLock) { + mIdleHandlers.remove(idler); + } + } + } + + // Reset the idle handler count to 0 so we do not run them again. + pendingIdleHandlerCount = 0; + + // While calling an idle handler, a new message could have been delivered + // so go back and look again for a pending message without waiting. + mNextPollTimeoutMillis = 0; + } + } + + void quit(boolean safe) { + if (!mQuitAllowed) { + throw new IllegalStateException("Main thread not allowed to quit."); + } + synchronized (mIdleHandlersLock) { + if (sQuitting.compareAndSet(this, false, true)) { + if (safe) { + removeAllFutureMessages(); + } else { + removeAllMessages(); + } + + // We can assume mPtr != 0 because sQuitting was previously false. + nativeWake(mPtr); + } + } + } + + boolean enqueueMessage(@NonNull Message msg, long when) { + if (msg.target == null) { + throw new IllegalArgumentException("Message must have a target."); + } + + if (msg.isInUse()) { + throw new IllegalStateException(msg + " This message is already in use."); + } + + return enqueueMessageUnchecked(msg, when); + } + + private boolean enqueueMessageUnchecked(@NonNull Message msg, long when) { + if ((boolean) sQuitting.getVolatile(this)) { + IllegalStateException e = new IllegalStateException( + msg.target + " sending message to a Handler on a dead thread"); + Log.w(TAG, e.getMessage(), e); + msg.recycleUnchecked(); + return false; + } + + long seq = when != 0 ? ((long)sNextInsertSeq.getAndAdd(this, 1L) + 1L) + : ((long)sNextFrontInsertSeq.getAndAdd(this, -1L) - 1L); + /* TODO: Add a MessageNode member to Message so we can avoid this allocation */ + MessageNode node = new MessageNode(msg, seq); + msg.when = when; + msg.markInUse(); + + if (DEBUG) { + Log.d(TAG, "Insert message what: " + msg.what + " when: " + msg.when + " seq: " + + node.mInsertSeq + " barrier: " + node.isBarrier() + " async: " + + node.isAsync() + " now: " + SystemClock.uptimeMillis()); + } + + while (true) { + StackNode old = (StackNode) sState.getVolatile(this); + boolean wakeNeeded; + boolean inactive; + + node.mNext = old; + switch (old.getNodeType()) { + case STACK_NODE_ACTIVE: + /* + * The worker thread is currently active and will process any elements added to + * the stack before parking again. + */ + node.mBottomOfStack = (StateNode) old; + inactive = false; + node.mWokeUp = true; + wakeNeeded = false; + break; + + case STACK_NODE_PARKED: + node.mBottomOfStack = (StateNode) old; + inactive = true; + node.mWokeUp = true; + wakeNeeded = true; + break; + + case STACK_NODE_TIMEDPARK: + node.mBottomOfStack = (StateNode) old; + inactive = true; + wakeNeeded = mStackStateTimedPark.mWhenToWake >= node.getWhen(); + node.mWokeUp = wakeNeeded; + break; + + default: + MessageNode oldMessage = (MessageNode) old; + + node.mBottomOfStack = oldMessage.mBottomOfStack; + int bottomType = node.mBottomOfStack.getNodeType(); + inactive = bottomType >= STACK_NODE_PARKED; + wakeNeeded = (bottomType == STACK_NODE_TIMEDPARK + && mStackStateTimedPark.mWhenToWake >= node.getWhen() + && !oldMessage.mWokeUp); + node.mWokeUp = oldMessage.mWokeUp || wakeNeeded; + break; + } + if (sState.compareAndSet(this, old, node)) { + if (inactive) { + if (wakeNeeded) { + nativeWake(mPtr); + } else { + mMessageCounts.incrementQueued(); + } + } + return true; + } + } + } + + /** + * Posts a synchronization barrier to the Looper's message queue. + * + * Message processing occurs as usual until the message queue encounters the + * synchronization barrier that has been posted. When the barrier is encountered, + * later synchronous messages in the queue are stalled (prevented from being executed) + * until the barrier is released by calling {@link #removeSyncBarrier} and specifying + * the token that identifies the synchronization barrier. + * + * This method is used to immediately postpone execution of all subsequently posted + * synchronous messages until a condition is met that releases the barrier. + * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier + * and continue to be processed as usual. + * + * This call must be always matched by a call to {@link #removeSyncBarrier} with + * the same token to ensure that the message queue resumes normal operation. + * Otherwise the application will probably hang! + * + * @return A token that uniquely identifies the barrier. This token must be + * passed to {@link #removeSyncBarrier} to release the barrier. + * + * @hide + */ + @TestApi + public int postSyncBarrier() { + return postSyncBarrier(SystemClock.uptimeMillis()); + } + + private int postSyncBarrier(long when) { + final int token = mNextBarrierToken.getAndIncrement(); + final Message msg = Message.obtain(); + + msg.markInUse(); + msg.arg1 = token; + + if (!enqueueMessageUnchecked(msg, when)) { + Log.wtf(TAG, "Unexpected error while adding sync barrier!"); + return -1; + } + + return token; + } + + private class MatchBarrierToken extends MessageCompare { + int mBarrierToken; + + MatchBarrierToken(int token) { + super(); + mBarrierToken = token; + } + + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == null && m.arg1 == mBarrierToken) { + return true; + } + return false; + } + } + + /** + * Removes a synchronization barrier. + * + * @param token The synchronization barrier token that was returned by + * {@link #postSyncBarrier}. + * + * @throws IllegalStateException if the barrier was not found. + * + * @hide + */ + @TestApi + public void removeSyncBarrier(int token) { + boolean removed; + MessageNode first; + final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token); + + try { + /* Retain the first element to see if we are currently stuck on a barrier. */ + first = mPriorityQueue.first(); + } catch (NoSuchElementException e) { + /* The queue is empty */ + first = null; + } + + removed = findOrRemoveMessages(null, 0, null, null, 0, matchBarrierToken, true); + if (removed && first != null) { + Message m = first.mMessage; + if (m.target == null && m.arg1 == token) { + /* Wake up next() in case it was sleeping on this barrier. */ + nativeWake(mPtr); + } + } else if (!removed) { + throw new IllegalStateException("The specified message queue synchronization " + + " barrier token has not been posted or has already been removed."); + } + } + + private StateNode getStateNode(StackNode node) { + if (node.isMessageNode()) { + return ((MessageNode) node).mBottomOfStack; + } + return (StateNode) node; + } + + private void waitForDrainCompleted() { + mDrainingLock.lock(); + while (mNextIsDrainingStack) { + mDrainCompleted.awaitUninterruptibly(); + } + mDrainingLock.unlock(); + } + + /* + * This class is used to find matches for hasMessages() and removeMessages() + */ + private abstract static class MessageCompare { + public abstract boolean compareMessage(Message m, Handler h, int what, Object object, + Runnable r, long when); + } + + private boolean stackHasMessages(Handler h, int what, Object object, Runnable r, long when, + MessageCompare compare, boolean removeMatches) { + boolean found = false; + StackNode top = (StackNode) sState.getVolatile(this); + StateNode bottom = getStateNode(top); + + /* + * If the top node is a state node, there are no reachable messages. + * If it's anything other than Active, we can quit as we know that next() is not + * consuming items. + * If the top node is Active then we know that next() is currently consuming items. + * In that case we should wait next() has drained the stack. + */ + if (top == bottom) { + if (bottom != sStackStateActive) { + return false; + } + waitForDrainCompleted(); + return false; + } + + /* + * We have messages that we may tombstone. Walk the stack until we hit the bottom or we + * hit a null pointer. + * If we hit the bottom, we are done. + * If we hit a null pointer, then the stack is being consumed by next() and we must cycle + * until the stack has been drained. + */ + MessageNode p = (MessageNode) top; + + while (true) { + if (compare.compareMessage(p.mMessage, h, what, object, r, when)) { + found = true; + if (DEBUG) { + Log.w(TAG, "stackHasMessages node matches"); + } + if (removeMatches) { + if (p.removeFromStack()) { + p.mMessage.recycleUnchecked(); + if (mMessageCounts.incrementCancelled()) { + nativeWake(mPtr); + } + } + } else { + return true; + } + } + + StackNode n = p.mNext; + if (n == null) { + /* Next() is walking the stack, we must re-sample */ + if (DEBUG) { + Log.d(TAG, "stackHasMessages next() is walking the stack, we must re-sample"); + } + waitForDrainCompleted(); + break; + } + if (!n.isMessageNode()) { + /* We reached the end of the stack */ + return found; + } + p = (MessageNode) n; + } + + return found; + } + + private boolean priorityQueueHasMessage(ConcurrentSkipListSet<MessageNode> queue, Handler h, + int what, Object object, Runnable r, long when, MessageCompare compare, + boolean removeMatches) { + Iterator<MessageNode> iterator = queue.iterator(); + boolean found = false; + + while (iterator.hasNext()) { + MessageNode msg = iterator.next(); + + if (compare.compareMessage(msg.mMessage, h, what, object, r, when)) { + if (removeMatches) { + found = true; + if (queue.remove(msg)) { + msg.mMessage.recycleUnchecked(); + } + } else { + return true; + } + } + } + return found; + } + + private boolean findOrRemoveMessages(Handler h, int what, Object object, Runnable r, long when, + MessageCompare compare, boolean removeMatches) { + boolean foundInStack, foundInQueue; + + foundInStack = stackHasMessages(h, what, object, r, when, compare, removeMatches); + foundInQueue = priorityQueueHasMessage(mPriorityQueue, h, what, object, r, when, compare, + removeMatches); + foundInQueue |= priorityQueueHasMessage(mAsyncPriorityQueue, h, what, object, r, when, + compare, removeMatches); + + return foundInStack || foundInQueue; + } + + private static class MatchHandlerWhatAndObject extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && m.what == what && (object == null || m.obj == object)) { + return true; + } + return false; + } + } + private final MatchHandlerWhatAndObject mMatchHandlerWhatAndObject = + new MatchHandlerWhatAndObject(); + boolean hasMessages(Handler h, int what, Object object) { + if (h == null) { + return false; + } + + return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, false); + } + + private static class MatchHandlerWhatAndObjectEquals extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) { + return true; + } + return false; + } + } + private final MatchHandlerWhatAndObjectEquals mMatchHandlerWhatAndObjectEquals = + new MatchHandlerWhatAndObjectEquals(); + boolean hasEqualMessages(Handler h, int what, Object object) { + if (h == null) { + return false; + } + + return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, + false); + } + + private static class MatchHandlerRunnableAndObject extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && m.callback == r && (object == null || m.obj == object)) { + return true; + } + return false; + } + } + private final MatchHandlerRunnableAndObject mMatchHandlerRunnableAndObject = + new MatchHandlerRunnableAndObject(); + + boolean hasMessages(Handler h, Runnable r, Object object) { + if (h == null) { + return false; + } + + return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, false); + } + + private static class MatchHandler extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h) { + return true; + } + return false; + } + } + private final MatchHandler mMatchHandler = new MatchHandler(); + boolean hasMessages(Handler h) { + if (h == null) { + return false; + } + return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false); + } + + void removeMessages(Handler h, int what, Object object) { + if (h == null) { + return; + } + findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true); + } + + void removeEqualMessages(Handler h, int what, Object object) { + if (h == null) { + return; + } + findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true); + } + + void removeMessages(Handler h, Runnable r, Object object) { + if (h == null || r == null) { + return; + } + findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true); + } + + private static class MatchHandlerRunnableAndObjectEquals extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) { + return true; + } + return false; + } + } + private final MatchHandlerRunnableAndObjectEquals mMatchHandlerRunnableAndObjectEquals = + new MatchHandlerRunnableAndObjectEquals(); + void removeEqualMessages(Handler h, Runnable r, Object object) { + if (h == null || r == null) { + return; + } + findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true); + } + + private static class MatchHandlerAndObject extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && (object == null || m.obj == object)) { + return true; + } + return false; + } + } + private final MatchHandlerAndObject mMatchHandlerAndObject = new MatchHandlerAndObject(); + void removeCallbacksAndMessages(Handler h, Object object) { + if (h == null) { + return; + } + findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true); + } + + private static class MatchHandlerAndObjectEquals extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && (object == null || object.equals(m.obj))) { + return true; + } + return false; + } + } + private final MatchHandlerAndObjectEquals mMatchHandlerAndObjectEquals = + new MatchHandlerAndObjectEquals(); + void removeCallbacksAndEqualMessages(Handler h, Object object) { + if (h == null) { + return; + } + findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true); + } + + private static class MatchAllMessages extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + return true; + } + } + private final MatchAllMessages mMatchAllMessages = new MatchAllMessages(); + private void removeAllMessages() { + findOrRemoveMessages(null, -1, null, null, 0, mMatchAllMessages, true); + } + + private static class MatchAllFutureMessages extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.when > when) { + return true; + } + return false; + } + } + private final MatchAllFutureMessages mMatchAllFutureMessages = new MatchAllFutureMessages(); + private void removeAllFutureMessages() { + findOrRemoveMessages(null, -1, null, null, SystemClock.uptimeMillis(), + mMatchAllFutureMessages, true); + } + + private void printPriorityQueueNodes() { + Iterator<MessageNode> iterator = mPriorityQueue.iterator(); + + Log.d(TAG, "* Dump priority queue"); + while (iterator.hasNext()) { + MessageNode msgNode = iterator.next(); + Log.d(TAG, "** MessageNode what: " + msgNode.mMessage.what + " when " + + msgNode.mMessage.when + " seq: " + msgNode.mInsertSeq); + } + } + + private int dumpPriorityQueue(ConcurrentSkipListSet<MessageNode> queue, Printer pw, + String prefix, Handler h, int n) { + int count = 0; + long now = SystemClock.uptimeMillis(); + + for (MessageNode msgNode : queue) { + Message msg = msgNode.mMessage; + if (h == null || h == msg.target) { + pw.println(prefix + "Message " + (n + count) + ": " + msg.toString(now)); + } + count++; + } + return count; + } + + void dump(Printer pw, String prefix, Handler h) { + long now = SystemClock.uptimeMillis(); + int n = 0; + + pw.println(prefix + "(MessageQueue is using Concurrent implementation)"); + + StackNode node = (StackNode) sState.getVolatile(this); + while (node != null) { + if (node.isMessageNode()) { + Message msg = ((MessageNode) node).mMessage; + if (h == null || h == msg.target) { + pw.println(prefix + "Message " + n + ": " + msg.toString(now)); + } + node = ((MessageNode) node).mNext; + } else { + pw.println(prefix + "State: " + node); + node = null; + } + n++; + } + + pw.println(prefix + "PriorityQueue Messages: "); + n += dumpPriorityQueue(mPriorityQueue, pw, prefix, h, n); + pw.println(prefix + "AsyncPriorityQueue Messages: "); + n += dumpPriorityQueue(mAsyncPriorityQueue, pw, prefix, h, n); + + pw.println(prefix + "(Total messages: " + n + ", polling=" + isPolling() + + ", quitting=" + (boolean) sQuitting.getVolatile(this) + ")"); + } + + private int dumpPriorityQueue(ConcurrentSkipListSet<MessageNode> queue, + ProtoOutputStream proto) { + int count = 0; + + for (MessageNode msgNode : queue) { + Message msg = msgNode.mMessage; + msg.dumpDebug(proto, MessageQueueProto.MESSAGES); + count++; + } + return count; + } + + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long messageQueueToken = proto.start(fieldId); + + StackNode node = (StackNode) sState.getVolatile(this); + while (node.isMessageNode()) { + Message msg = ((MessageNode) node).mMessage; + msg.dumpDebug(proto, MessageQueueProto.MESSAGES); + node = ((MessageNode) node).mNext; + } + + dumpPriorityQueue(mPriorityQueue, proto); + dumpPriorityQueue(mAsyncPriorityQueue, proto); + + proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPolling()); + proto.write(MessageQueueProto.IS_QUITTING, (boolean) sQuitting.getVolatile(this)); + proto.end(messageQueueToken); + } + + /** + * Adds a file descriptor listener to receive notification when file descriptor + * related events occur. + * <p> + * If the file descriptor has already been registered, the specified events + * and listener will replace any that were previously associated with it. + * It is not possible to set more than one listener per file descriptor. + * </p><p> + * It is important to always unregister the listener when the file descriptor + * is no longer of use. + * </p> + * + * @param fd The file descriptor for which a listener will be registered. + * @param events The set of events to receive: a combination of the + * {@link OnFileDescriptorEventListener#EVENT_INPUT}, + * {@link OnFileDescriptorEventListener#EVENT_OUTPUT}, and + * {@link OnFileDescriptorEventListener#EVENT_ERROR} event masks. If the requested + * set of events is zero, then the listener is unregistered. + * @param listener The listener to invoke when file descriptor events occur. + * + * @see OnFileDescriptorEventListener + * @see #removeOnFileDescriptorEventListener + */ + @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) + public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd, + @OnFileDescriptorEventListener.Events int events, + @NonNull OnFileDescriptorEventListener listener) { + if (fd == null) { + throw new IllegalArgumentException("fd must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mFileDescriptorRecordsLock) { + updateOnFileDescriptorEventListenerLocked(fd, events, listener); + } + } + + /** + * Removes a file descriptor listener. + * <p> + * This method does nothing if no listener has been registered for the + * specified file descriptor. + * </p> + * + * @param fd The file descriptor whose listener will be unregistered. + * + * @see OnFileDescriptorEventListener + * @see #addOnFileDescriptorEventListener + */ + @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) + public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) { + if (fd == null) { + throw new IllegalArgumentException("fd must not be null"); + } + + synchronized (mFileDescriptorRecordsLock) { + updateOnFileDescriptorEventListenerLocked(fd, 0, null); + } + } + + @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) + private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events, + OnFileDescriptorEventListener listener) { + final int fdNum = fd.getInt$(); + + int index = -1; + FileDescriptorRecord record = null; + if (mFileDescriptorRecords != null) { + index = mFileDescriptorRecords.indexOfKey(fdNum); + if (index >= 0) { + record = mFileDescriptorRecords.valueAt(index); + if (record != null && record.mEvents == events) { + return; + } + } + } + + if (events != 0) { + events |= OnFileDescriptorEventListener.EVENT_ERROR; + if (record == null) { + if (mFileDescriptorRecords == null) { + mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>(); + } + record = new FileDescriptorRecord(fd, events, listener); + mFileDescriptorRecords.put(fdNum, record); + } else { + record.mListener = listener; + record.mEvents = events; + record.mSeq += 1; + } + nativeSetFileDescriptorEvents(mPtr, fdNum, events); + } else if (record != null) { + record.mEvents = 0; + mFileDescriptorRecords.removeAt(index); + nativeSetFileDescriptorEvents(mPtr, fdNum, 0); + } + } + + // Called from native code. + private int dispatchEvents(int fd, int events) { + // Get the file descriptor record and any state that might change. + final FileDescriptorRecord record; + final int oldWatchedEvents; + final OnFileDescriptorEventListener listener; + final int seq; + synchronized (mFileDescriptorRecordsLock) { + record = mFileDescriptorRecords.get(fd); + if (record == null) { + return 0; // spurious, no listener registered + } + + oldWatchedEvents = record.mEvents; + events &= oldWatchedEvents; // filter events based on current watched set + if (events == 0) { + return oldWatchedEvents; // spurious, watched events changed + } + + listener = record.mListener; + seq = record.mSeq; + } + + // Invoke the listener outside of the lock. + int newWatchedEvents = listener.onFileDescriptorEvents( + record.mDescriptor, events); + if (newWatchedEvents != 0) { + newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR; + } + + // Update the file descriptor record if the listener changed the set of + // events to watch and the listener itself hasn't been updated since. + if (newWatchedEvents != oldWatchedEvents) { + synchronized (mFileDescriptorRecordsLock) { + int index = mFileDescriptorRecords.indexOfKey(fd); + if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record + && record.mSeq == seq) { + record.mEvents = newWatchedEvents; + if (newWatchedEvents == 0) { + mFileDescriptorRecords.removeAt(index); + } + } + } + } + + // Return the new set of events to watch for native code to take care of. + return newWatchedEvents; + } + + /** + * Callback interface for discovering when a thread is going to block + * waiting for more messages. + */ + public static interface IdleHandler { + /** + * Called when the message queue has run out of messages and will now + * wait for more. Return true to keep your idle handler active, false + * to have it removed. This may be called if there are still messages + * pending in the queue, but they are all scheduled to be dispatched + * after the current time. + */ + boolean queueIdle(); + } + + /** + * A listener which is invoked when file descriptor related events occur. + */ + public interface OnFileDescriptorEventListener { + /** + * File descriptor event: Indicates that the file descriptor is ready for input + * operations, such as reading. + * <p> + * The listener should read all available data from the file descriptor + * then return <code>true</code> to keep the listener active or <code>false</code> + * to remove the listener. + * </p><p> + * In the case of a socket, this event may be generated to indicate + * that there is at least one incoming connection that the listener + * should accept. + * </p><p> + * This event will only be generated if the {@link #EVENT_INPUT} event mask was + * specified when the listener was added. + * </p> + */ + public static final int EVENT_INPUT = 1 << 0; + + /** + * File descriptor event: Indicates that the file descriptor is ready for output + * operations, such as writing. + * <p> + * The listener should write as much data as it needs. If it could not + * write everything at once, then it should return <code>true</code> to + * keep the listener active. Otherwise, it should return <code>false</code> + * to remove the listener then re-register it later when it needs to write + * something else. + * </p><p> + * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was + * specified when the listener was added. + * </p> + */ + public static final int EVENT_OUTPUT = 1 << 1; + + /** + * File descriptor event: Indicates that the file descriptor encountered a + * fatal error. + * <p> + * File descriptor errors can occur for various reasons. One common error + * is when the remote peer of a socket or pipe closes its end of the connection. + * </p><p> + * This event may be generated at any time regardless of whether the + * {@link #EVENT_ERROR} event mask was specified when the listener was added. + * </p> + */ + public static final int EVENT_ERROR = 1 << 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "EVENT_" }, value = { + EVENT_INPUT, + EVENT_OUTPUT, + EVENT_ERROR + }) + public @interface Events {} + + /** + * Called when a file descriptor receives events. + * + * @param fd The file descriptor. + * @param events The set of events that occurred: a combination of the + * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks. + * @return The new set of events to watch, or 0 to unregister the listener. + * + * @see #EVENT_INPUT + * @see #EVENT_OUTPUT + * @see #EVENT_ERROR + */ + @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events); + } + + static final class FileDescriptorRecord { + public final FileDescriptor mDescriptor; + public int mEvents; + public OnFileDescriptorEventListener mListener; + public int mSeq; + + public FileDescriptorRecord(FileDescriptor descriptor, + int events, OnFileDescriptorEventListener listener) { + mDescriptor = descriptor; + mEvents = events; + mListener = listener; + } + } +} diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java index 5b711c9d8401..6b9b3496d1c0 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java @@ -20,6 +20,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.os.Handler; +import android.os.Process; +import android.os.Trace; import android.util.Log; import android.util.Printer; import android.util.SparseArray; @@ -29,6 +32,7 @@ import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicLong; /** * Low-level class holding the list of messages to be dispatched by a @@ -44,6 +48,7 @@ import java.util.ArrayList; public final class MessageQueue { private static final String TAG = "MessageQueue"; private static final boolean DEBUG = false; + private static final boolean TRACE = false; // True if the message queue can be quit. @UnsupportedAppUsage @@ -326,6 +331,8 @@ public final class MessageQueue { return newWatchedEvents; } + private static final AtomicLong mMessagesDelivered = new AtomicLong(); + @UnsupportedAppUsage Message next() { // Return here if the message loop has already quit and been disposed. @@ -381,6 +388,9 @@ public final class MessageQueue { if (msg.isAsynchronous()) { mAsyncMessageCount--; } + if (TRACE) { + Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet()); + } return msg; } } else { @@ -794,7 +804,7 @@ public final class MessageQueue { Message n = p.next; if (n != null) { if (n.target == h && n.what == what - && (object == null || n.obj == object)) { + && (object == null || n.obj == object)) { Message nn = n.next; if (n.isAsynchronous()) { mAsyncMessageCount--; @@ -841,7 +851,7 @@ public final class MessageQueue { Message n = p.next; if (n != null) { if (n.target == h && n.what == what - && (object == null || object.equals(n.obj))) { + && (object == null || object.equals(n.obj))) { Message nn = n.next; if (n.isAsynchronous()) { mAsyncMessageCount--; @@ -888,7 +898,7 @@ public final class MessageQueue { Message n = p.next; if (n != null) { if (n.target == h && n.callback == r - && (object == null || n.obj == object)) { + && (object == null || n.obj == object)) { Message nn = n.next; if (n.isAsynchronous()) { mAsyncMessageCount--; @@ -935,7 +945,7 @@ public final class MessageQueue { Message n = p.next; if (n != null) { if (n.target == h && n.callback == r - && (object == null || object.equals(n.obj))) { + && (object == null || object.equals(n.obj))) { Message nn = n.next; if (n.isAsynchronous()) { mAsyncMessageCount--; @@ -1093,6 +1103,7 @@ public final class MessageQueue { void dump(Printer pw, String prefix, Handler h) { synchronized (this) { + pw.println(prefix + "(MessageQueue is using Legacy implementation)"); long now = SystemClock.uptimeMillis(); int n = 0; for (Message msg = mMessages; msg != null; msg = msg.next) { diff --git a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java new file mode 100644 index 000000000000..967332fcf80c --- /dev/null +++ b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java @@ -0,0 +1,1589 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Handler; +import android.os.Trace; +import android.util.Log; +import android.util.Printer; +import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; + +import java.io.FileDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.PriorityQueue; +import java.util.PriorityQueue; +import java.util.PriorityQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Low-level class holding the list of messages to be dispatched by a + * {@link Looper}. Messages are not added directly to a MessageQueue, + * but rather through {@link Handler} objects associated with the Looper. + * + * <p>You can retrieve the MessageQueue for the current thread with + * {@link Looper#myQueue() Looper.myQueue()}. + */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass +@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( + "com.android.platform.test.ravenwood.nativesubstitution.MessageQueue_host") +public final class MessageQueue { + private static final String TAG = "SemiConcurrentMessageQueue"; + private static final boolean DEBUG = false; + private static final boolean TRACE = false; + + // True if the message queue can be quit. + private final boolean mQuitAllowed; + + @SuppressWarnings("unused") + private long mPtr; // used by native code + + @IntDef(value = { + STACK_NODE_MESSAGE, + STACK_NODE_ACTIVE, + STACK_NODE_PARKED, + STACK_NODE_TIMEDPARK}) + @Retention(RetentionPolicy.SOURCE) + private @interface StackNodeType {} + + /* + * Stack node types. STACK_NODE_MESSAGE indicates a node containing a message. + * The other types indicate what state our Looper thread is in. The bottom of + * the stack is always a single state node. Message nodes are added on top. + */ + private static final int STACK_NODE_MESSAGE = 0; + /* + * Active state indicates that next() is processing messages + */ + private static final int STACK_NODE_ACTIVE = 1; + /* + * Parked state indicates that the Looper thread is sleeping indefinitely (nothing to deliver) + */ + private static final int STACK_NODE_PARKED = 2; + /* + * Timed Park state indicates that the Looper thread is sleeping, waiting for a message + * deadline + */ + private static final int STACK_NODE_TIMEDPARK = 3; + + /* Describes a node in the Treiber stack */ + static class StackNode { + @StackNodeType + private final int mType; + + StackNode(@StackNodeType int type) { + mType = type; + } + + @StackNodeType + final int getNodeType() { + return mType; + } + + final boolean isMessageNode() { + return mType == STACK_NODE_MESSAGE; + } + } + + static final class MessageNode extends StackNode implements Comparable<MessageNode> { + private final Message mMessage; + volatile StackNode mNext; + StateNode mBottomOfStack; + boolean mWokeUp; + boolean mRemovedFromStack = false; + final long mInsertSeq; + + MessageNode(@NonNull Message message, long insertSeq) { + super(STACK_NODE_MESSAGE); + mMessage = message; + mInsertSeq = insertSeq; + } + + long getWhen() { + return mMessage.when; + } + + boolean isRemovedFromStack() { + return mRemovedFromStack; + } + + boolean removeFromStack() { + if (!mRemovedFromStack) { + mRemovedFromStack = true; + return true; + } + return false; + } + + boolean isAsync() { + return mMessage.isAsynchronous(); + } + + boolean isBarrier() { + return mMessage.target == null; + } + + @Override + public int compareTo(@NonNull MessageNode messageNode) { + Message other = messageNode.mMessage; + + int compared = Long.compare(mMessage.when, other.when); + if (compared == 0) { + compared = Long.compare(mInsertSeq, messageNode.mInsertSeq); + } + return compared; + } + } + + static class StateNode extends StackNode { + StateNode(int type) { + super(type); + } + } + + static final class TimedParkStateNode extends StateNode { + long mWhenToWake; + + TimedParkStateNode() { + super(STACK_NODE_TIMEDPARK); + } + } + + private static final StateNode sStackStateActive = new StateNode(STACK_NODE_ACTIVE); + private static final StateNode sStackStateParked = new StateNode(STACK_NODE_PARKED); + private final TimedParkStateNode mStackStateTimedPark = new TimedParkStateNode(); + + /* This is the top of our treiber stack. */ + private static final VarHandle sState; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + sState = l.findVarHandle(MessageQueue.class, "mStateValue", + MessageQueue.StackNode.class); + } catch (Exception e) { + Log.wtf(TAG, "VarHandle lookup failed with exception: " + e); + throw new ExceptionInInitializerError(e); + } + } + + private volatile StackNode mStateValue = sStackStateParked; + @GuardedBy("mPriorityQueue") + private final PriorityQueue<MessageNode> mPriorityQueue = + new PriorityQueue<MessageNode>(); + @GuardedBy("mPriorityQueue") + private final PriorityQueue<MessageNode> mAsyncPriorityQueue = + new PriorityQueue<MessageNode>(); + + /* + * This helps us ensure that messages with the same timestamp are inserted in FIFO order. + * Increments on each insert, starting at 0. MessageNode.compareTo() will compare sequences + * when delivery timestamps are identical. + */ + private static final VarHandle sNextInsertSeq; + private volatile long mNextInsertSeqValue = 0; + /* + * The exception to the FIFO order rule is sendMessageAtFrontOfQueue(). + * Those messages must be in LIFO order - SIGH. + * Decrements on each front of queue insert. + */ + private static final VarHandle sNextFrontInsertSeq; + private volatile long mNextFrontInsertSeqValue = -1; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + sNextInsertSeq = l.findVarHandle(MessageQueue.class, "mNextInsertSeqValue", + long.class); + sNextFrontInsertSeq = l.findVarHandle(MessageQueue.class, "mNextFrontInsertSeqValue", + long.class); + } catch (Exception e) { + Log.wtf(TAG, "VarHandle lookup failed with exception: " + e); + throw new ExceptionInInitializerError(e); + } + + } + + /* + * Tracks the number of queued and cancelled messages in our stack. + * + * On item cancellation, determine whether to wake next() to flush tombstoned messages. + * We track queued and cancelled counts as two ints packed into a single long. + */ + private static final class MessageCounts { + private static VarHandle sCounts; + private volatile long mCountsValue = 0; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + sCounts = l.findVarHandle(MessageQueue.MessageCounts.class, "mCountsValue", + long.class); + } catch (Exception e) { + Log.wtf(TAG, "VarHandle lookup failed with exception: " + e); + throw new ExceptionInInitializerError(e); + } + } + /* We use a special value to indicate when next() has been woken for flush. */ + private static final long AWAKE = Long.MAX_VALUE; + /* + * Minimum number of messages in the stack which we need before we consider flushing + * tombstoned items. + */ + private static final int MESSAGE_FLUSH_THRESHOLD = 10; + + private static int numQueued(long val) { + return (int) (val >>> Integer.SIZE); + } + + private static int numCancelled(long val) { + return (int) val; + } + + private static long combineCounts(int queued, int cancelled) { + return ((long) queued << Integer.SIZE) | (long) cancelled; + } + + public void incrementQueued() { + while (true) { + long oldVal = mCountsValue; + int queued = numQueued(oldVal); + int cancelled = numCancelled(oldVal); + /* Use Math.max() to avoid overflow of queued count */ + long newVal = combineCounts(Math.max(queued + 1, queued), cancelled); + + /* Don't overwrite 'AWAKE' state */ + if (oldVal == AWAKE || sCounts.compareAndSet(this, oldVal, newVal)) { + break; + } + } + } + + public boolean incrementCancelled() { + while (true) { + long oldVal = mCountsValue; + if (oldVal == AWAKE) { + return false; + } + int queued = numQueued(oldVal); + int cancelled = numCancelled(oldVal); + boolean needsPurge = queued > MESSAGE_FLUSH_THRESHOLD + && (queued >> 1) < cancelled; + long newVal; + if (needsPurge) { + newVal = AWAKE; + } else { + newVal = combineCounts(queued, + Math.max(cancelled + 1, cancelled)); + } + + if (sCounts.compareAndSet(this, oldVal, newVal)) { + return needsPurge; + } + } + } + + public void clearCounts() { + mCountsValue = 0; + } + } + + private final MessageCounts mMessageCounts = new MessageCounts(); + + private final Object mIdleHandlersLock = new Object(); + @GuardedBy("mIdleHandlersLock") + private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); + private IdleHandler[] mPendingIdleHandlers; + + private final Object mFileDescriptorRecordsLock = new Object(); + @GuardedBy("mFileDescriptorRecordsLock") + private SparseArray<FileDescriptorRecord> mFileDescriptorRecords; + + private static final VarHandle sQuitting; + private boolean mQuittingValue = false; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + sQuitting = l.findVarHandle(MessageQueue.class, "mQuittingValue", boolean.class); + } catch (Exception e) { + Log.wtf(TAG, "VarHandle lookup failed with exception: " + e); + throw new ExceptionInInitializerError(e); + } + } + + // The next barrier token. + // Barriers are indicated by messages with a null target whose arg1 field carries the token. + private final AtomicInteger mNextBarrierToken = new AtomicInteger(1); + + private static native long nativeInit(); + private static native void nativeDestroy(long ptr); + private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/ + private static native void nativeWake(long ptr); + private static native boolean nativeIsPolling(long ptr); + private static native void nativeSetFileDescriptorEvents(long ptr, int fd, int events); + + MessageQueue(boolean quitAllowed) { + mQuitAllowed = quitAllowed; + mPtr = nativeInit(); + } + + @Override + protected void finalize() throws Throwable { + try { + dispose(); + } finally { + super.finalize(); + } + } + + // Disposes of the underlying message queue. + // Must only be called on the looper thread or the finalizer. + private void dispose() { + if (mPtr != 0) { + nativeDestroy(mPtr); + mPtr = 0; + } + } + + /** + * Returns true if the looper has no pending messages which are due to be processed. + * + * <p>This method is safe to call from any thread. + * + * @return True if the looper is idle. + */ + public boolean isIdle() { + MessageNode msgNode = null; + MessageNode asyncMsgNode = null; + + synchronized (mPriorityQueue) { + msgNode = mPriorityQueue.peek(); + asyncMsgNode = mAsyncPriorityQueue.peek(); + + final long now = SystemClock.uptimeMillis(); + if ((msgNode != null && msgNode.getWhen() <= now) + || (asyncMsgNode != null && asyncMsgNode.getWhen() <= now)) { + return false; + } + } + + return true; + } + + /** + * Add a new {@link IdleHandler} to this message queue. This may be + * removed automatically for you by returning false from + * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is + * invoked, or explicitly removing it with {@link #removeIdleHandler}. + * + * <p>This method is safe to call from any thread. + * + * @param handler The IdleHandler to be added. + */ + public void addIdleHandler(@NonNull IdleHandler handler) { + if (handler == null) { + throw new NullPointerException("Can't add a null IdleHandler"); + } + synchronized (mIdleHandlersLock) { + mIdleHandlers.add(handler); + } + } + + /** + * Remove an {@link IdleHandler} from the queue that was previously added + * with {@link #addIdleHandler}. If the given object is not currently + * in the idle list, nothing is done. + * + * <p>This method is safe to call from any thread. + * + * @param handler The IdleHandler to be removed. + */ + public void removeIdleHandler(@NonNull IdleHandler handler) { + synchronized (mIdleHandlersLock) { + mIdleHandlers.remove(handler); + } + } + + /** + * Returns whether this looper's thread is currently polling for more work to do. + * This is a good signal that the loop is still alive rather than being stuck + * handling a callback. Note that this method is intrinsically racy, since the + * state of the loop can change before you get the result back. + * + * <p>This method is safe to call from any thread. + * + * @return True if the looper is currently polling for events. + * @hide + */ + public boolean isPolling() { + // If the loop is quitting then it must not be idling. + // We can assume mPtr != 0 when sQuitting is false. + return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr); + } + + /* Helper to choose the correct queue to insert into. */ + @GuardedBy("mPriorityQueue") + private void insertIntoPriorityQueue(MessageNode msgNode) { + if (msgNode.isAsync()) { + mAsyncPriorityQueue.offer(msgNode); + } else { + mPriorityQueue.offer(msgNode); + } + } + + @GuardedBy("mPriorityQueue") + private boolean removeFromPriorityQueue(MessageNode msgNode) { + if (msgNode.isAsync()) { + return mAsyncPriorityQueue.remove(msgNode); + } else { + return mPriorityQueue.remove(msgNode); + } + } + + private MessageNode pickEarliestNode(MessageNode nodeA, MessageNode nodeB) { + if (nodeA != null && nodeB != null) { + if (nodeA.compareTo(nodeB) < 0) { + return nodeA; + } + return nodeB; + } + + return nodeA != null ? nodeA : nodeB; + } + + /* Move any non-cancelled messages into the priority queue */ + private void drainStack(StackNode oldTop) { + while (oldTop.isMessageNode()) { + MessageNode oldTopMessageNode = (MessageNode) oldTop; + if (oldTopMessageNode.removeFromStack()) { + insertIntoPriorityQueue(oldTopMessageNode); + } + MessageNode inserted = oldTopMessageNode; + oldTop = oldTopMessageNode.mNext; + } + } + + /* Set the stack state to Active, return a list of nodes to walk. */ + private StackNode swapAndSetStackStateActive() { + while (true) { + /* Set stack state to Active, get node list to walk later */ + StackNode current = (StackNode) sState.getVolatile(this); + if (current == sStackStateActive + || sState.compareAndSet(this, current, sStackStateActive)) { + return current; + } + } + } + + /* This is only read/written from the Looper thread */ + private int mNextPollTimeoutMillis; + private static final AtomicLong mMessagesDelivered = new AtomicLong(); + + private Message nextMessage() { + int i = 0; + + while (true) { + if (DEBUG) { + Log.d(TAG, "nextMessage loop #" + i); + i++; + } + + /* This protects us from racing with remove. Enqueue can still add items. */ + synchronized (mPriorityQueue) { + + /* + * Set our state to active, drain any items from the stack into our priority queues + */ + StackNode oldTop; + oldTop = swapAndSetStackStateActive(); + drainStack(oldTop); + + /* + * The objective of this next block of code is to: + * - find a message to return (if any is ready) + * - find a next message we would like to return, after scheduling. + * - we make our scheduling decision based on this next message (if it exists). + * + * We have two queues to juggle and the presence of barriers throws an additional + * wrench into our plans. + */ + + /* Get the first node from each queue */ + MessageNode msgNode = mPriorityQueue.peek(); + MessageNode asyncMsgNode = mAsyncPriorityQueue.peek(); + + if (DEBUG) { + if (msgNode != null) { + Message msg = msgNode.mMessage; + Log.d(TAG, "Next found node what: " + msg.what + " when: " + msg.when + + " seq: " + msgNode.mInsertSeq + "barrier: " + + msgNode.isBarrier() + " now: " + + SystemClock.uptimeMillis()); + } + if (asyncMsgNode != null) { + Message msg = asyncMsgNode.mMessage; + Log.d(TAG, "Next found async node what: " + msg.what + " when: " + msg.when + + " seq: " + asyncMsgNode.mInsertSeq + "barrier: " + + asyncMsgNode.isBarrier() + " now: " + + SystemClock.uptimeMillis()); + } + } + + /* + * the node which we will return, null if none are ready + */ + MessageNode found = null; + /* + * The node from which we will determine our next wakeup time. + * Null indicates there is no next message ready. If we found a node, + * we can leave this null as Looper will call us again after delivering + * the message. + */ + MessageNode next = null; + + long now = SystemClock.uptimeMillis(); + /* + * If we have a barrier we should return the async node if it exists and is + * ready + */ + if (msgNode != null && msgNode.isBarrier()) { + if (asyncMsgNode != null && now >= asyncMsgNode.getWhen()) { + found = asyncMsgNode; + removeFromPriorityQueue(found); + } else { + next = asyncMsgNode; + } + } else { /* No barrier. */ + MessageNode earliest; + /* + * If we have two messages, pick the earliest option from either queue. + * Otherwise grab whichever node is non-null. If both are null we'll fall + * through. + */ + earliest = pickEarliestNode(msgNode, asyncMsgNode); + + if (earliest != null) { + if (now >= earliest.getWhen()) { + found = earliest; + removeFromPriorityQueue(found); + } else { + next = earliest; + } + } + } + + if (DEBUG) { + if (found != null) { + Message msg = found.mMessage; + Log.d(TAG, "Will deliver node what: " + msg.what + " when: " + msg.when + + " seq: " + found.mInsertSeq + " barrier: " + + found.isBarrier() + " async: " + found.isAsync() + + " now: " + SystemClock.uptimeMillis()); + } else { + Log.d(TAG, "No node to deliver"); + } + if (next != null) { + Message msg = next.mMessage; + Log.d(TAG, "Next node what: " + msg.what + " when: " + msg.when + " seq: " + + next.mInsertSeq + " barrier: " + next.isBarrier() + + " async: " + next.isAsync() + + " now: " + SystemClock.uptimeMillis()); + } else { + Log.d(TAG, "No next node"); + } + } + + /* + * If we have a found message, we will get called again so there's no need to set + * state. + * In that case we can leave our state as ACTIVE. + * + * Otherwise we should determine how to park the thread. + */ + StateNode nextOp = sStackStateActive; + if (found == null) { + if (next == null) { + /* No message to deliver, sleep indefinitely */ + mNextPollTimeoutMillis = -1; + nextOp = sStackStateParked; + if (DEBUG) { + Log.d(TAG, "nextMessage next state is StackStateParked"); + } + } else { + /* Message not ready, or we found one to deliver already, set a timeout */ + long nextMessageWhen = next.getWhen(); + if (nextMessageWhen > now) { + mNextPollTimeoutMillis = (int) Math.min(nextMessageWhen - now, + Integer.MAX_VALUE); + } else { + mNextPollTimeoutMillis = 0; + } + + mStackStateTimedPark.mWhenToWake = now + mNextPollTimeoutMillis; + nextOp = mStackStateTimedPark; + if (DEBUG) { + Log.d(TAG, "nextMessage next state is StackStateTimedParked " + + " next timeout ms " + mNextPollTimeoutMillis + + " mWhenToWake: " + mStackStateTimedPark.mWhenToWake + + " now " + now); + } + } + } + + /* + * Try to swap our state from Active back to Park or TimedPark. If we raced with + * enqueue, loop back around to pick up any new items. + */ + if (sState.compareAndSet(this, sStackStateActive, nextOp)) { + mMessageCounts.clearCounts(); + if (found != null) { + if (TRACE) { + Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet()); + } + return found.mMessage; + } + return null; + } + if (found != null) { + /* + * Add this node back - we will be adding new nodes into our priority queue, and + * recalculating what to return. + */ + insertIntoPriorityQueue(found); + } + } + } + } + + Message next() { + final long ptr = mPtr; + if (ptr == 0) { + return null; + } + + mNextPollTimeoutMillis = 0; + int pendingIdleHandlerCount = -1; // -1 only during first iteration + while (true) { + if (mNextPollTimeoutMillis != 0) { + Binder.flushPendingCommands(); + } + + nativePollOnce(ptr, mNextPollTimeoutMillis); + + Message msg = nextMessage(); + if (msg != null) { + msg.markInUse(); + return msg; + } + + if ((boolean) sQuitting.getVolatile(this)) { + return null; + } + + synchronized (mIdleHandlersLock) { + // If first time idle, then get the number of idlers to run. + // Idle handles only run if the queue is empty or if the first message + // in the queue (possibly a barrier) is due to be handled in the future. + if (pendingIdleHandlerCount < 0 + && mNextPollTimeoutMillis != 0) { + pendingIdleHandlerCount = mIdleHandlers.size(); + } + if (pendingIdleHandlerCount <= 0) { + // No idle handlers to run. Loop and wait some more. + continue; + } + + if (mPendingIdleHandlers == null) { + mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; + } + mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); + } + + // Run the idle handlers. + // We only ever reach this code block during the first iteration. + for (int i = 0; i < pendingIdleHandlerCount; i++) { + final IdleHandler idler = mPendingIdleHandlers[i]; + mPendingIdleHandlers[i] = null; // release the reference to the handler + + boolean keep = false; + try { + keep = idler.queueIdle(); + } catch (Throwable t) { + Log.wtf(TAG, "IdleHandler threw exception", t); + } + + if (!keep) { + synchronized (mIdleHandlersLock) { + mIdleHandlers.remove(idler); + } + } + } + + // Reset the idle handler count to 0 so we do not run them again. + pendingIdleHandlerCount = 0; + + // While calling an idle handler, a new message could have been delivered + // so go back and look again for a pending message without waiting. + mNextPollTimeoutMillis = 0; + } + } + + void quit(boolean safe) { + if (!mQuitAllowed) { + throw new IllegalStateException("Main thread not allowed to quit."); + } + synchronized (mIdleHandlersLock) { + if (sQuitting.compareAndSet(this, false, true)) { + if (safe) { + removeAllFutureMessages(); + } else { + removeAllMessages(); + } + + // We can assume mPtr != 0 because sQuitting was previously false. + nativeWake(mPtr); + } + } + } + + boolean enqueueMessage(@NonNull Message msg, long when) { + if (msg.target == null) { + throw new IllegalArgumentException("Message must have a target."); + } + + if (msg.isInUse()) { + throw new IllegalStateException(msg + " This message is already in use."); + } + + return enqueueMessageUnchecked(msg, when); + } + + private boolean enqueueMessageUnchecked(@NonNull Message msg, long when) { + if ((boolean) sQuitting.getVolatile(this)) { + IllegalStateException e = new IllegalStateException( + msg.target + " sending message to a Handler on a dead thread"); + Log.w(TAG, e.getMessage(), e); + msg.recycleUnchecked(); + return false; + } + + long seq = when != 0 ? ((long)sNextInsertSeq.getAndAdd(this, 1L) + 1L) + : ((long)sNextFrontInsertSeq.getAndAdd(this, -1L) - 1L); + /* TODO: Add a MessageNode member to Message so we can avoid this allocation */ + MessageNode node = new MessageNode(msg, seq); + msg.when = when; + msg.markInUse(); + + if (DEBUG) { + Log.d(TAG, "Insert message what: " + msg.what + " when: " + msg.when + " seq: " + + node.mInsertSeq + " barrier: " + node.isBarrier() + " async: " + + node.isAsync() + " now: " + SystemClock.uptimeMillis()); + } + + while (true) { + StackNode old = (StackNode) sState.getVolatile(this); + boolean wakeNeeded; + boolean inactive; + + node.mNext = old; + switch (old.getNodeType()) { + case STACK_NODE_ACTIVE: + /* + * The worker thread is currently active and will process any elements added to + * the stack before parking again. + */ + node.mBottomOfStack = (StateNode) old; + inactive = false; + node.mWokeUp = true; + wakeNeeded = false; + break; + + case STACK_NODE_PARKED: + node.mBottomOfStack = (StateNode) old; + inactive = true; + node.mWokeUp = true; + wakeNeeded = true; + break; + + case STACK_NODE_TIMEDPARK: + node.mBottomOfStack = (StateNode) old; + inactive = true; + wakeNeeded = mStackStateTimedPark.mWhenToWake >= node.getWhen(); + node.mWokeUp = wakeNeeded; + break; + + default: + MessageNode oldMessage = (MessageNode) old; + + node.mBottomOfStack = oldMessage.mBottomOfStack; + int bottomType = node.mBottomOfStack.getNodeType(); + inactive = bottomType >= STACK_NODE_PARKED; + wakeNeeded = (bottomType == STACK_NODE_TIMEDPARK + && mStackStateTimedPark.mWhenToWake >= node.getWhen() + && !oldMessage.mWokeUp); + node.mWokeUp = oldMessage.mWokeUp || wakeNeeded; + break; + } + if (sState.compareAndSet(this, old, node)) { + if (inactive) { + if (wakeNeeded) { + nativeWake(mPtr); + } else { + mMessageCounts.incrementQueued(); + } + } + return true; + } + } + } + + /** + * Posts a synchronization barrier to the Looper's message queue. + * + * Message processing occurs as usual until the message queue encounters the + * synchronization barrier that has been posted. When the barrier is encountered, + * later synchronous messages in the queue are stalled (prevented from being executed) + * until the barrier is released by calling {@link #removeSyncBarrier} and specifying + * the token that identifies the synchronization barrier. + * + * This method is used to immediately postpone execution of all subsequently posted + * synchronous messages until a condition is met that releases the barrier. + * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier + * and continue to be processed as usual. + * + * This call must be always matched by a call to {@link #removeSyncBarrier} with + * the same token to ensure that the message queue resumes normal operation. + * Otherwise the application will probably hang! + * + * @return A token that uniquely identifies the barrier. This token must be + * passed to {@link #removeSyncBarrier} to release the barrier. + * + * @hide + */ + @TestApi + public int postSyncBarrier() { + return postSyncBarrier(SystemClock.uptimeMillis()); + } + + private int postSyncBarrier(long when) { + final int token = mNextBarrierToken.getAndIncrement(); + final Message msg = Message.obtain(); + + msg.markInUse(); + msg.arg1 = token; + + if (!enqueueMessageUnchecked(msg, when)) { + Log.wtf(TAG, "Unexpected error while adding sync barrier!"); + return -1; + } + + return token; + } + + private class MatchBarrierToken extends MessageCompare { + int mBarrierToken; + + MatchBarrierToken(int token) { + super(); + mBarrierToken = token; + } + + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == null && m.arg1 == mBarrierToken) { + return true; + } + return false; + } + } + + /** + * Removes a synchronization barrier. + * + * @param token The synchronization barrier token that was returned by + * {@link #postSyncBarrier}. + * + * @throws IllegalStateException if the barrier was not found. + * + * @hide + */ + @TestApi + public void removeSyncBarrier(int token) { + boolean removed; + MessageNode first; + final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token); + + synchronized (mPriorityQueue) { + try { + /* Retain the first element to see if we are currently stuck on a barrier. */ + first = mPriorityQueue.peek(); + } catch (NoSuchElementException e) { + /* The queue is empty */ + first = null; + } + + removed = findOrRemoveMessages(null, 0, null, null, 0, matchBarrierToken, true); + if (removed && first != null) { + Message m = first.mMessage; + if (m.target == null && m.arg1 == token) { + /* Wake up next() in case it was sleeping on this barrier. */ + nativeWake(mPtr); + } + } else if (!removed) { + throw new IllegalStateException("The specified message queue synchronization " + + " barrier token has not been posted or has already been removed."); + } + } + } + + private StateNode getStateNode(StackNode node) { + if (node.isMessageNode()) { + return ((MessageNode) node).mBottomOfStack; + } + return (StateNode) node; + } + + /* + * This class is used to find matches for hasMessages() and removeMessages() + */ + private abstract static class MessageCompare { + public abstract boolean compareMessage(Message m, Handler h, int what, Object object, + Runnable r, long when); + } + @GuardedBy("mPriorityQueue") + private boolean stackHasMessages(Handler h, int what, Object object, Runnable r, long when, + MessageCompare compare, boolean removeMatches) { + boolean found = false; + StackNode top = (StackNode) sState.getVolatile(this); + StateNode bottom = getStateNode(top); + + /* No messages to search. */ + if (!top.isMessageNode()) { + return false; + } + + /* + * We have messages that we may tombstone. Walk the stack until we hit the bottom. + * next() will remove them on it's next pass. + */ + if (!(top instanceof MessageNode)) { + Log.wtf(TAG, "Unknown node type found in Trieber stack"); + } + MessageNode p = (MessageNode) top; + + while (true) { + if (compare.compareMessage(p.mMessage, h, what, object, r, when)) { + found = true; + if (DEBUG) { + Log.w(TAG, "stackHasMessages node matches"); + } + if (removeMatches) { + if (p.removeFromStack()) { + p.mMessage.recycleUnchecked(); + if (mMessageCounts.incrementCancelled()) { + nativeWake(mPtr); + } + } + } else { + return true; + } + } + + StackNode n = p.mNext; + if (!n.isMessageNode()) { + /* We reached the end of the stack */ + return found; + } + p = (MessageNode) n; + } + } + + @GuardedBy("mPriorityQueue") + private boolean priorityQueueHasMessage(PriorityQueue queue, Handler h, + int what, Object object, Runnable r, long when, MessageCompare compare, + boolean removeMatches) { + Iterator<MessageNode> iterator = queue.iterator(); + boolean found = false; + + while (iterator.hasNext()) { + MessageNode msg = iterator.next(); + + if (compare.compareMessage(msg.mMessage, h, what, object, r, when)) { + if (removeMatches) { + found = true; + iterator.remove(); + msg.mMessage.recycleUnchecked(); + } else { + return true; + } + } + } + return found; + } + + private boolean findOrRemoveMessages(Handler h, int what, Object object, Runnable r, long when, + MessageCompare compare, boolean removeMatches) { + boolean foundInStack, foundInQueue; + + synchronized (mPriorityQueue) { + foundInStack = stackHasMessages(h, what, object, r, when, compare, removeMatches); + foundInQueue = priorityQueueHasMessage(mPriorityQueue, h, what, object, r, when, + compare, removeMatches); + foundInQueue |= priorityQueueHasMessage(mAsyncPriorityQueue, h, what, object, r, when, + compare, removeMatches); + + return foundInStack || foundInQueue; + } + } + + private static class MatchHandlerWhatAndObject extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && m.what == what && (object == null || m.obj == object)) { + return true; + } + return false; + } + } + private final MatchHandlerWhatAndObject mMatchHandlerWhatAndObject = + new MatchHandlerWhatAndObject(); + boolean hasMessages(Handler h, int what, Object object) { + if (h == null) { + return false; + } + + return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, false); + } + + private static class MatchHandlerWhatAndObjectEquals extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) { + return true; + } + return false; + } + } + private final MatchHandlerWhatAndObjectEquals mMatchHandlerWhatAndObjectEquals = + new MatchHandlerWhatAndObjectEquals(); + boolean hasEqualMessages(Handler h, int what, Object object) { + if (h == null) { + return false; + } + + return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, + false); + } + + private static class MatchHandlerRunnableAndObject extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && m.callback == r && (object == null || m.obj == object)) { + return true; + } + return false; + } + } + private final MatchHandlerRunnableAndObject mMatchHandlerRunnableAndObject = + new MatchHandlerRunnableAndObject(); + + boolean hasMessages(Handler h, Runnable r, Object object) { + if (h == null) { + return false; + } + + return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, false); + } + + private static class MatchHandler extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h) { + return true; + } + return false; + } + } + private final MatchHandler mMatchHandler = new MatchHandler(); + boolean hasMessages(Handler h) { + if (h == null) { + return false; + } + return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false); + } + + void removeMessages(Handler h, int what, Object object) { + if (h == null) { + return; + } + findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true); + } + + void removeEqualMessages(Handler h, int what, Object object) { + if (h == null) { + return; + } + findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true); + } + + void removeMessages(Handler h, Runnable r, Object object) { + if (h == null || r == null) { + return; + } + findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true); + } + + private static class MatchHandlerRunnableAndObjectEquals extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) { + return true; + } + return false; + } + } + private final MatchHandlerRunnableAndObjectEquals mMatchHandlerRunnableAndObjectEquals = + new MatchHandlerRunnableAndObjectEquals(); + void removeEqualMessages(Handler h, Runnable r, Object object) { + if (h == null || r == null) { + return; + } + findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true); + } + + private static class MatchHandlerAndObject extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && (object == null || m.obj == object)) { + return true; + } + return false; + } + } + private final MatchHandlerAndObject mMatchHandlerAndObject = new MatchHandlerAndObject(); + void removeCallbacksAndMessages(Handler h, Object object) { + if (h == null) { + return; + } + findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true); + } + + private static class MatchHandlerAndObjectEquals extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.target == h && (object == null || object.equals(m.obj))) { + return true; + } + return false; + } + } + private final MatchHandlerAndObjectEquals mMatchHandlerAndObjectEquals = + new MatchHandlerAndObjectEquals(); + void removeCallbacksAndEqualMessages(Handler h, Object object) { + if (h == null) { + return; + } + findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true); + } + + private static class MatchAllMessages extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + return true; + } + } + private final MatchAllMessages mMatchAllMessages = new MatchAllMessages(); + private void removeAllMessages() { + findOrRemoveMessages(null, -1, null, null, 0, mMatchAllMessages, true); + } + + private static class MatchAllFutureMessages extends MessageCompare { + @Override + public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r, + long when) { + if (m.when > when) { + return true; + } + return false; + } + } + private final MatchAllFutureMessages mMatchAllFutureMessages = new MatchAllFutureMessages(); + private void removeAllFutureMessages() { + findOrRemoveMessages(null, -1, null, null, SystemClock.uptimeMillis(), + mMatchAllFutureMessages, true); + } + + private void printPriorityQueueNodes() { + Iterator<MessageNode> iterator = mPriorityQueue.iterator(); + + Log.d(TAG, "* Dump priority queue"); + while (iterator.hasNext()) { + MessageNode msgNode = iterator.next(); + Log.d(TAG, "** MessageNode what: " + msgNode.mMessage.what + " when " + + msgNode.mMessage.when + " seq: " + msgNode.mInsertSeq); + } + } + + private int dumpPriorityQueue(PriorityQueue<MessageNode> queue, Printer pw, String prefix, + Handler h, int n) { + int count = 0; + long now = SystemClock.uptimeMillis(); + + for (MessageNode msgNode : queue) { + Message msg = msgNode.mMessage; + if (h == null || h == msg.target) { + pw.println(prefix + "Message " + (n + count) + ": " + msg.toString(now)); + } + count++; + } + return count; + } + + void dump(Printer pw, String prefix, Handler h) { + long now = SystemClock.uptimeMillis(); + int n = 0; + + pw.println(prefix + "(MessageQueue is using SemiConcurrent implementation)"); + + StackNode node = (StackNode) sState.getVolatile(this); + while (node != null) { + if (node.isMessageNode()) { + Message msg = ((MessageNode) node).mMessage; + if (h == null || h == msg.target) { + pw.println(prefix + "Message " + n + ": " + msg.toString(now)); + } + node = ((MessageNode) node).mNext; + } else { + pw.println(prefix + "State: " + node); + node = null; + } + n++; + } + + synchronized (mPriorityQueue) { + pw.println(prefix + "PriorityQueue Messages: "); + n += dumpPriorityQueue(mPriorityQueue, pw, prefix, h, n); + pw.println(prefix + "AsyncPriorityQueue Messages: "); + n += dumpPriorityQueue(mAsyncPriorityQueue, pw, prefix, h, n); + } + + pw.println(prefix + "(Total messages: " + n + ", polling=" + isPolling() + + ", quitting=" + (boolean) sQuitting.getVolatile(this) + ")"); + } + + private int dumpPriorityQueue(PriorityQueue<MessageNode> queue, ProtoOutputStream proto) { + int count = 0; + + for (MessageNode msgNode : queue) { + Message msg = msgNode.mMessage; + msg.dumpDebug(proto, MessageQueueProto.MESSAGES); + count++; + } + return count; + } + + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long messageQueueToken = proto.start(fieldId); + + StackNode node = (StackNode) sState.getVolatile(this); + while (node.isMessageNode()) { + Message msg = ((MessageNode) node).mMessage; + msg.dumpDebug(proto, MessageQueueProto.MESSAGES); + node = ((MessageNode) node).mNext; + } + + synchronized (mPriorityQueue) { + dumpPriorityQueue(mPriorityQueue, proto); + dumpPriorityQueue(mAsyncPriorityQueue, proto); + } + + proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPolling()); + proto.write(MessageQueueProto.IS_QUITTING, (boolean) sQuitting.getVolatile(this)); + proto.end(messageQueueToken); + } + + /** + * Adds a file descriptor listener to receive notification when file descriptor + * related events occur. + * <p> + * If the file descriptor has already been registered, the specified events + * and listener will replace any that were previously associated with it. + * It is not possible to set more than one listener per file descriptor. + * </p><p> + * It is important to always unregister the listener when the file descriptor + * is no longer of use. + * </p> + * + * @param fd The file descriptor for which a listener will be registered. + * @param events The set of events to receive: a combination of the + * {@link OnFileDescriptorEventListener#EVENT_INPUT}, + * {@link OnFileDescriptorEventListener#EVENT_OUTPUT}, and + * {@link OnFileDescriptorEventListener#EVENT_ERROR} event masks. If the requested + * set of events is zero, then the listener is unregistered. + * @param listener The listener to invoke when file descriptor events occur. + * + * @see OnFileDescriptorEventListener + * @see #removeOnFileDescriptorEventListener + */ + @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) + public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd, + @OnFileDescriptorEventListener.Events int events, + @NonNull OnFileDescriptorEventListener listener) { + if (fd == null) { + throw new IllegalArgumentException("fd must not be null"); + } + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mFileDescriptorRecordsLock) { + updateOnFileDescriptorEventListenerLocked(fd, events, listener); + } + } + + /** + * Removes a file descriptor listener. + * <p> + * This method does nothing if no listener has been registered for the + * specified file descriptor. + * </p> + * + * @param fd The file descriptor whose listener will be unregistered. + * + * @see OnFileDescriptorEventListener + * @see #addOnFileDescriptorEventListener + */ + @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) + public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) { + if (fd == null) { + throw new IllegalArgumentException("fd must not be null"); + } + + synchronized (mFileDescriptorRecordsLock) { + updateOnFileDescriptorEventListenerLocked(fd, 0, null); + } + } + + @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class) + private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events, + OnFileDescriptorEventListener listener) { + final int fdNum = fd.getInt$(); + + int index = -1; + FileDescriptorRecord record = null; + if (mFileDescriptorRecords != null) { + index = mFileDescriptorRecords.indexOfKey(fdNum); + if (index >= 0) { + record = mFileDescriptorRecords.valueAt(index); + if (record != null && record.mEvents == events) { + return; + } + } + } + + if (events != 0) { + events |= OnFileDescriptorEventListener.EVENT_ERROR; + if (record == null) { + if (mFileDescriptorRecords == null) { + mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>(); + } + record = new FileDescriptorRecord(fd, events, listener); + mFileDescriptorRecords.put(fdNum, record); + } else { + record.mListener = listener; + record.mEvents = events; + record.mSeq += 1; + } + nativeSetFileDescriptorEvents(mPtr, fdNum, events); + } else if (record != null) { + record.mEvents = 0; + mFileDescriptorRecords.removeAt(index); + nativeSetFileDescriptorEvents(mPtr, fdNum, 0); + } + } + + // Called from native code. + private int dispatchEvents(int fd, int events) { + // Get the file descriptor record and any state that might change. + final FileDescriptorRecord record; + final int oldWatchedEvents; + final OnFileDescriptorEventListener listener; + final int seq; + synchronized (mFileDescriptorRecordsLock) { + record = mFileDescriptorRecords.get(fd); + if (record == null) { + return 0; // spurious, no listener registered + } + + oldWatchedEvents = record.mEvents; + events &= oldWatchedEvents; // filter events based on current watched set + if (events == 0) { + return oldWatchedEvents; // spurious, watched events changed + } + + listener = record.mListener; + seq = record.mSeq; + } + + // Invoke the listener outside of the lock. + int newWatchedEvents = listener.onFileDescriptorEvents( + record.mDescriptor, events); + if (newWatchedEvents != 0) { + newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR; + } + + // Update the file descriptor record if the listener changed the set of + // events to watch and the listener itself hasn't been updated since. + if (newWatchedEvents != oldWatchedEvents) { + synchronized (mFileDescriptorRecordsLock) { + int index = mFileDescriptorRecords.indexOfKey(fd); + if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record + && record.mSeq == seq) { + record.mEvents = newWatchedEvents; + if (newWatchedEvents == 0) { + mFileDescriptorRecords.removeAt(index); + } + } + } + } + + // Return the new set of events to watch for native code to take care of. + return newWatchedEvents; + } + + /** + * Callback interface for discovering when a thread is going to block + * waiting for more messages. + */ + public static interface IdleHandler { + /** + * Called when the message queue has run out of messages and will now + * wait for more. Return true to keep your idle handler active, false + * to have it removed. This may be called if there are still messages + * pending in the queue, but they are all scheduled to be dispatched + * after the current time. + */ + boolean queueIdle(); + } + + /** + * A listener which is invoked when file descriptor related events occur. + */ + public interface OnFileDescriptorEventListener { + /** + * File descriptor event: Indicates that the file descriptor is ready for input + * operations, such as reading. + * <p> + * The listener should read all available data from the file descriptor + * then return <code>true</code> to keep the listener active or <code>false</code> + * to remove the listener. + * </p><p> + * In the case of a socket, this event may be generated to indicate + * that there is at least one incoming connection that the listener + * should accept. + * </p><p> + * This event will only be generated if the {@link #EVENT_INPUT} event mask was + * specified when the listener was added. + * </p> + */ + public static final int EVENT_INPUT = 1 << 0; + + /** + * File descriptor event: Indicates that the file descriptor is ready for output + * operations, such as writing. + * <p> + * The listener should write as much data as it needs. If it could not + * write everything at once, then it should return <code>true</code> to + * keep the listener active. Otherwise, it should return <code>false</code> + * to remove the listener then re-register it later when it needs to write + * something else. + * </p><p> + * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was + * specified when the listener was added. + * </p> + */ + public static final int EVENT_OUTPUT = 1 << 1; + + /** + * File descriptor event: Indicates that the file descriptor encountered a + * fatal error. + * <p> + * File descriptor errors can occur for various reasons. One common error + * is when the remote peer of a socket or pipe closes its end of the connection. + * </p><p> + * This event may be generated at any time regardless of whether the + * {@link #EVENT_ERROR} event mask was specified when the listener was added. + * </p> + */ + public static final int EVENT_ERROR = 1 << 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "EVENT_" }, value = { + EVENT_INPUT, + EVENT_OUTPUT, + EVENT_ERROR + }) + public @interface Events {} + + /** + * Called when a file descriptor receives events. + * + * @param fd The file descriptor. + * @param events The set of events that occurred: a combination of the + * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks. + * @return The new set of events to watch, or 0 to unregister the listener. + * + * @see #EVENT_INPUT + * @see #EVENT_OUTPUT + * @see #EVENT_ERROR + */ + @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events); + } + + static final class FileDescriptorRecord { + public final FileDescriptor mDescriptor; + public int mEvents; + public OnFileDescriptorEventListener mListener; + public int mSeq; + + public FileDescriptorRecord(FileDescriptor descriptor, + int events, OnFileDescriptorEventListener listener) { + mDescriptor = descriptor; + mEvents = events; + mListener = listener; + } + } +} diff --git a/core/java/android/os/connectivity/CellularBatteryStats.java b/core/java/android/os/connectivity/CellularBatteryStats.java index fc17002ba056..1649ed56c648 100644 --- a/core/java/android/os/connectivity/CellularBatteryStats.java +++ b/core/java/android/os/connectivity/CellularBatteryStats.java @@ -15,11 +15,13 @@ */ package android.os.connectivity; +import android.annotation.FlaggedApi; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.BatteryStats; import android.os.Parcel; import android.os.Parcelable; @@ -35,6 +37,7 @@ import java.util.Objects; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass @SystemApi public final class CellularBatteryStats implements Parcelable { @@ -83,11 +86,17 @@ public final class CellularBatteryStats implements Parcelable { } }; - /** @hide **/ + /** + * This constructor should only be used in tests. + * @hide + */ + @FlaggedApi( + com.android.server.power.optimization.Flags.FLAG_STREAMLINED_CONNECTIVITY_BATTERY_STATS) + @TestApi public CellularBatteryStats(long loggingDurationMs, long kernelActiveTimeMs, long numPacketsTx, long numBytesTx, long numPacketsRx, long numBytesRx, long sleepTimeMs, long idleTimeMs, - long rxTimeMs, Long energyConsumedMaMs, long[] timeInRatMs, - long[] timeInRxSignalStrengthLevelMs, long[] txTimeMs, + long rxTimeMs, long energyConsumedMaMs, @NonNull long[] timeInRatMs, + @NonNull long[] timeInRxSignalStrengthLevelMs, @NonNull long[] txTimeMs, long monitoredRailChargeConsumedMaMs) { mLoggingDurationMs = loggingDurationMs; @@ -270,7 +279,6 @@ public final class CellularBatteryStats implements Parcelable { * @return The amount of time the phone spends in the {@code networkType} network type. The * unit is in microseconds. */ - @NonNull @SuppressLint("MethodNameUnits") public long getTimeInRatMicros(@NetworkType int networkType) { if (networkType >= mTimeInRatMs.length) { @@ -289,7 +297,6 @@ public final class CellularBatteryStats implements Parcelable { * @return Amount of time phone spends in specific cellular rx signal strength levels * in microseconds. The index is signal strength bin. */ - @NonNull @SuppressLint("MethodNameUnits") public long getTimeInRxSignalStrengthLevelMicros( @IntRange(from = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, @@ -315,10 +322,9 @@ public final class CellularBatteryStats implements Parcelable { * <li> index 3 = 15dBm < tx_power < 20dBm. </li> * <li> index 4 = tx_power > 20dBm. </li> * </ul> - * - * @hide */ - @NonNull + @FlaggedApi( + com.android.server.power.optimization.Flags.FLAG_STREAMLINED_CONNECTIVITY_BATTERY_STATS) public long getTxTimeMillis( @IntRange(from = ModemActivityInfo.TX_POWER_LEVEL_0, to = ModemActivityInfo.TX_POWER_LEVEL_4) int level) { diff --git a/core/java/android/os/connectivity/WifiBatteryStats.java b/core/java/android/os/connectivity/WifiBatteryStats.java index 7e6ebcfc61db..79e0be803aa1 100644 --- a/core/java/android/os/connectivity/WifiBatteryStats.java +++ b/core/java/android/os/connectivity/WifiBatteryStats.java @@ -19,9 +19,11 @@ import static android.os.BatteryStats.NUM_WIFI_SIGNAL_STRENGTH_BINS; import static android.os.BatteryStatsManager.NUM_WIFI_STATES; import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -34,6 +36,7 @@ import java.util.Objects; * @hide */ @SystemApi +@android.ravenwood.annotation.RavenwoodKeepWholeClass public final class WifiBatteryStats implements Parcelable { private final long mLoggingDurationMillis; private final long mKernelActiveTimeMillis; @@ -150,7 +153,13 @@ public final class WifiBatteryStats implements Parcelable { mMonitoredRailChargeConsumedMaMillis); } - /** @hide **/ + /** + * This constructor should only be used in tests. + * @hide + */ + @FlaggedApi( + com.android.server.power.optimization.Flags.FLAG_STREAMLINED_CONNECTIVITY_BATTERY_STATS) + @TestApi public WifiBatteryStats(long loggingDurationMillis, long kernelActiveTimeMillis, long numPacketsTx, long numBytesTx, long numPacketsRx, long numBytesRx, long sleepTimeMillis, long scanTimeMillis, long idleTimeMillis, long rxTimeMillis, diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 493d6760e5eb..57853e7ded32 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1403,6 +1403,19 @@ public final class Settings { "android.settings.QUICK_LAUNCH_SETTINGS"; /** + * Activity Action: Showing settings to manage adaptive notifications. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_ADAPTIVE_NOTIFICATIONS = + "android.settings.MANAGE_ADAPTIVE_NOTIFICATIONS"; + + /** * Activity Action: Show settings to manage installed applications. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -20094,7 +20107,7 @@ public final class Settings { * (0 = false, 1 = true) * @hide */ - @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @Readable public static final String REDUCE_MOTION = "reduce_motion"; /** diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 88bd87e6c27c..d019bad68cd5 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -4940,7 +4940,7 @@ public final class Telephony { * * @hide */ - public static final String COLUMN_IS_NTN = "is_ntn"; + public static final String COLUMN_IS_ONLY_NTN = "is_only_ntn"; /** * TelephonyProvider column name for transferred status @@ -4976,6 +4976,15 @@ public final class Telephony { public static final String COLUMN_SATELLITE_ENTITLEMENT_PLMNS = "satellite_entitlement_plmns"; + /** + * TelephonyProvider column name to indicate the satellite ESOS supported. The value of this + * column is set based on {@link CarrierConfigManager#KEY_SATELLITE_ESOS_SUPPORTED_BOOL}. + * By default, it's disabled. + * + * @hide + */ + public static final String COLUMN_SATELLITE_ESOS_SUPPORTED = "satellite_esos_supported"; + /** All columns in {@link SimInfo} table. */ private static final List<String> ALL_COLUMNS = List.of( COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID, @@ -5047,11 +5056,12 @@ public final class Telephony { COLUMN_USER_HANDLE, COLUMN_SATELLITE_ENABLED, COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER, - COLUMN_IS_NTN, + COLUMN_IS_ONLY_NTN, COLUMN_SERVICE_CAPABILITIES, COLUMN_TRANSFER_STATUS, COLUMN_SATELLITE_ENTITLEMENT_STATUS, - COLUMN_SATELLITE_ENTITLEMENT_PLMNS + COLUMN_SATELLITE_ENTITLEMENT_PLMNS, + COLUMN_SATELLITE_ESOS_SUPPORTED ); /** diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 8ecb1fb4e9ad..2948129ae956 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -19,6 +19,7 @@ package android.service.dreams; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.service.dreams.Flags.dreamHandlesConfirmKeys; import static android.service.dreams.Flags.dreamHandlesBeingObscured; +import static android.service.dreams.Flags.startAndStopDozingInBackground; import android.annotation.FlaggedApi; import android.annotation.IdRes; @@ -923,9 +924,16 @@ public class DreamService extends Service implements Window.Callback { if (mDozing) { try { - mDreamManager.startDozing( + if (startAndStopDozingInBackground()) { + mDreamManager.startDozingOneway( mDreamToken, mDozeScreenState, mDozeScreenStateReason, mDozeScreenBrightness); + } else { + mDreamManager.startDozing( + mDreamToken, mDozeScreenState, mDozeScreenStateReason, + mDozeScreenBrightness); + } + } catch (RemoteException ex) { // system server died } @@ -1250,7 +1258,11 @@ public class DreamService extends Service implements Window.Callback { try { // finishSelf will unbind the dream controller from the dream service. This will // trigger DreamService.this.onDestroy and DreamService.this will die. - mDreamManager.finishSelf(mDreamToken, true /*immediate*/); + if (startAndStopDozingInBackground()) { + mDreamManager.finishSelfOneway(mDreamToken, true /*immediate*/); + } else { + mDreamManager.finishSelf(mDreamToken, true /*immediate*/); + } } catch (RemoteException ex) { // system server died } diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index cf98bfe05faf..620eef66959f 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -50,4 +50,6 @@ interface IDreamManager { void startDreamActivity(in Intent intent); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)") oneway void setDreamIsObscured(in boolean isObscured); + oneway void startDozingOneway(in IBinder token, int screenState, int reason, int screenBrightness); + oneway void finishSelfOneway(in IBinder token, boolean immediate); } diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig index 54d950c18af8..83e0adfca3be 100644 --- a/core/java/android/service/dreams/flags.aconfig +++ b/core/java/android/service/dreams/flags.aconfig @@ -47,3 +47,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "start_and_stop_dozing_in_background" + namespace: "systemui" + description: "Move the start-dozing and stop-dozing operation to the background" + bug: "330287187" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig index d7389bab8d6d..be60c2504f38 100644 --- a/core/java/android/tracing/flags.aconfig +++ b/core/java/android/tracing/flags.aconfig @@ -38,3 +38,11 @@ flag { is_fixed_read_only: true bug: "323166383" } + +flag { + name: "perfetto_wm_tracing" + namespace: "windowing_tools" + description: "Migrate WindowManager tracing to Perfetto" + is_fixed_read_only: true + bug: "323165543" +} diff --git a/core/java/android/view/IPinnedTaskListener.aidl b/core/java/android/view/IPinnedTaskListener.aidl index e4e2d6f30aab..3bd150609625 100644 --- a/core/java/android/view/IPinnedTaskListener.aidl +++ b/core/java/android/view/IPinnedTaskListener.aidl @@ -42,12 +42,4 @@ oneway interface IPinnedTaskListener { * with fromImeAdjustement set to {@code true}. */ void onImeVisibilityChanged(boolean imeVisible, int imeHeight); - - /** - * Called by the window manager to notify the listener that Activity (was or is in pinned mode) - * is hidden (either stopped or removed). This is generally used as a signal to reset saved - * reentry fraction and size. - * {@param componentName} represents the application component of PiP window. - */ - void onActivityHidden(in ComponentName componentName); } diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java index fc1852d739e2..c7e93c19484f 100644 --- a/core/java/android/view/ImeBackAnimationController.java +++ b/core/java/android/view/ImeBackAnimationController.java @@ -27,6 +27,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.WindowConfiguration; import android.graphics.Insets; import android.util.Log; import android.view.animation.BackGestureInterpolator; @@ -137,9 +138,10 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { @Override public void onBackInvoked() { if (!isBackAnimationAllowed() || !mIsPreCommitAnimationInProgress) { - // play regular hide animation if back-animation is not allowed or if insets control has - // been cancelled by the system (this can happen in split screen for example) - mInsetsController.hide(ime()); + // play regular hide animation if predictive back-animation is not allowed or if insets + // control has been cancelled by the system. This can happen in multi-window mode for + // example (i.e. split-screen or activity-embedding) + notifyHideIme(); return; } startPostCommitAnim(/*hideIme*/ true); @@ -209,6 +211,11 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { if (triggerBack) { mInsetsController.setPredictiveBackImeHideAnimInProgress(true); notifyHideIme(); + // requesting IME as invisible during post-commit + mInsetsController.setRequestedVisibleTypes(0, ime()); + // Changes the animation state. This also notifies RootView of changed insets, which + // causes it to reset its scrollY to 0f (animated) if it was panned + mInsetsController.onAnimationStateChanged(ime(), /*running*/ true); } if (mStartRootScrollY != 0 && !triggerBack) { // This causes RootView to update its scroll back to the panned position @@ -228,12 +235,6 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { // the IME away mInsetsController.getHost().getInputMethodManager() .notifyImeHidden(mInsetsController.getHost().getWindowToken(), statsToken); - - // requesting IME as invisible during post-commit - mInsetsController.setRequestedVisibleTypes(0, ime()); - // Changes the animation state. This also notifies RootView of changed insets, which causes - // it to reset its scrollY to 0f (animated) if it was panned - mInsetsController.onAnimationStateChanged(ime(), /*running*/ true); } private void reset() { @@ -254,8 +255,18 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { } private boolean isBackAnimationAllowed() { - // back animation is allowed in all cases except when softInputMode is adjust_resize AND - // there is no app-registered WindowInsetsAnimationCallback AND edge-to-edge is not enabled. + + if (mViewRoot.mContext.getResources().getConfiguration().windowConfiguration + .getWindowingMode() == WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW) { + // TODO(b/346726115) enable predictive back animation in multi-window mode in + // DisplayImeController + return false; + } + + // otherwise, the predictive back animation is allowed in all cases except when + // 1. softInputMode is adjust_resize AND + // 2. there is no app-registered WindowInsetsAnimationCallback AND + // 3. edge-to-edge is not enabled. return (mViewRoot.mWindowAttributes.softInputMode & SOFT_INPUT_MASK_ADJUST) != SOFT_INPUT_ADJUST_RESIZE || (mViewRoot.mView != null && mViewRoot.mView.hasWindowInsetsAnimationCallback()) diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index f6535243eaac..634469dd52ff 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -72,7 +72,6 @@ import android.view.Surface.OutOfResourcesException; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; -import com.android.internal.util.VirtualRefBasePtr; import com.android.window.flags.Flags; import dalvik.system.CloseGuard; @@ -273,10 +272,12 @@ public final class SurfaceControl implements Parcelable { String windowName, int displayId); private static native void nativeSetFrameTimelineVsync(long transactionObj, long frameTimelineVsyncId); - private static native void nativeAddJankDataListener(long nativeListener, - long nativeSurfaceControl); - private static native void nativeRemoveJankDataListener(long nativeListener); - private static native long nativeCreateJankDataListenerWrapper(OnJankDataListener listener); + private static native long nativeCreateJankDataListenerWrapper( + long surfaceControl, OnJankDataListener listener); + private static native long nativeGetJankDataListenerWrapperFinalizer(); + private static native void nativeAddJankDataListener(long nativeListener); + private static native void nativeFlushJankData(long nativeListener); + private static native void nativeRemoveJankDataListener(long nativeListener, long afterVsync); private static native int nativeGetGPUContextPriority(); private static native void nativeSetTransformHint(long nativeObject, @SurfaceControl.BufferTransform int transformHint); @@ -461,17 +462,63 @@ public final class SurfaceControl implements Parcelable { * @see #addJankDataListener * @hide */ - public static abstract class OnJankDataListener { - private final VirtualRefBasePtr mNativePtr; + public interface OnJankDataListener { + /** + * Called when new jank classifications are available. + */ + void onJankDataAvailable(JankData[] jankData); + + } + + /** + * Handle to a registered {@link OnJankDatalistener}. + * @hide + */ + public static class OnJankDataListenerRegistration { + private final long mNativeObject; + + private static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + OnJankDataListenerRegistration.class.getClassLoader(), + nativeGetJankDataListenerWrapperFinalizer()); + + private final Runnable mFreeNativeResources; + private boolean mRemoved = false; - public OnJankDataListener() { - mNativePtr = new VirtualRefBasePtr(nativeCreateJankDataListenerWrapper(this)); + OnJankDataListenerRegistration(SurfaceControl surface, OnJankDataListener listener) { + mNativeObject = nativeCreateJankDataListenerWrapper(surface.mNativeObject, listener); + mFreeNativeResources = (mNativeObject == 0) ? () -> {} : + sRegistry.registerNativeAllocation(this, mNativeObject); } /** - * Called when new jank classifications are available. + * Request a flush of any pending jank classification data. May cause the registered + * listener to be invoked inband. */ - public abstract void onJankDataAvailable(JankData[] jankStats); + public void flush() { + nativeFlushJankData(mNativeObject); + } + + /** + * Request the removal of the registered listener after the VSync with the provided ID. Use + * a value <= 0 for afterVsync to remove the listener immediately. The given listener will + * not be removed before the given VSync, but may still reveive data for frames past the + * provided VSync. + */ + public void removeAfter(long afterVsync) { + mRemoved = true; + nativeRemoveJankDataListener(mNativeObject, afterVsync); + } + + /** + * Free the native resources associated with the listener registration. + */ + public void release() { + if (!mRemoved) { + removeAfter(0); + } + mFreeNativeResources.run(); + } } private final CloseGuard mCloseGuard = CloseGuard.get(); @@ -2632,19 +2679,11 @@ public final class SurfaceControl implements Parcelable { } /** - * Adds a callback to be informed about SF's jank classification for a specific surface. - * @hide - */ - public static void addJankDataListener(OnJankDataListener listener, SurfaceControl surface) { - nativeAddJankDataListener(listener.mNativePtr.get(), surface.mNativeObject); - } - - /** - * Removes a jank callback previously added with {@link #addJankDataListener} + * Adds a callback to be informed about SF's jank classification for this surface. * @hide */ - public static void removeJankDataListener(OnJankDataListener listener) { - nativeRemoveJankDataListener(listener.mNativePtr.get()); + public OnJankDataListenerRegistration addJankDataListener(OnJankDataListener listener) { + return new OnJankDataListenerRegistration(this, listener); } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 47669427cb9d..766e02bf3198 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -13859,6 +13859,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, }) @ResolvedLayoutDir public int getLayoutDirection() { + final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; + if (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1) { + mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED; + return LAYOUT_DIRECTION_RESOLVED_DEFAULT; + } return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) == PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR; } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index c0bd535f95f3..1525bd1d4af7 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -114,6 +114,7 @@ import static android.view.accessibility.Flags.fixMergedContentChangeEventV2; import static android.view.accessibility.Flags.forceInvertColor; import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle; import static android.view.flags.Flags.addSchandleToVriSurface; +import static android.view.flags.Flags.disableDrawWakeLock; import static android.view.flags.Flags.sensitiveContentAppProtection; import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix; import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly; @@ -149,6 +150,8 @@ import android.app.ResourcesManager; import android.app.WindowConfiguration; import android.app.compat.CompatChanges; import android.app.servertransaction.WindowStateTransactionItem; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ClipDescription; @@ -337,6 +340,15 @@ public final class ViewRootImpl implements ViewParent, private static final int LOGTAG_VIEWROOT_DRAW_EVENT = 60004; /** + * This change disables the {@code DRAW_WAKE_LOCK}, an internal wakelock acquired per-frame + * duration display DOZE. It was added to allow animation during AOD. This wakelock consumes + * battery severely if the animation is too heavy, so, it will be removed. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + private static final long DISABLE_DRAW_WAKE_LOCK = 349153669L; + + /** * Set to false if we do not want to use the multi threaded renderer even though * threaded renderer (aka hardware renderering) is used. Note that by disabling * this, WindowCallbacks will not fire. @@ -2459,6 +2471,11 @@ public final class ViewRootImpl implements ViewParent, } void pokeDrawLockIfNeeded() { + // Disable DRAW_WAKE_LOCK starting U. Otherwise, only need to acquire it for DOZE state. + if (CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK) && disableDrawWakeLock()) { + return; + } + if (!Display.isDozeState(mAttachInfo.mDisplayState)) { // Only need to acquire wake lock for DOZE state. return; @@ -7451,6 +7468,10 @@ public final class ViewRootImpl implements ViewParent, @Override protected int onProcess(QueuedInputEvent q) { + if (q.forPreImeOnly()) { + // this event is intended for the ViewPreImeInputStage only, let's forward + return FORWARD; + } if (q.mEvent instanceof KeyEvent) { final KeyEvent keyEvent = (KeyEvent) q.mEvent; diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java index 199a69a723d8..5b1c7d54ddb7 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -256,6 +256,21 @@ public class AutofillFeatureFlags { "ignore_relayout_auth_pending"; /** + * Fixes to handle apps relaying out, and causing problems for autofill. + * + * @hide + */ + public static final String DEVICE_CONFIG_ENABLE_RELAYOUT = "enable_relayout"; + + /** + * Enable relative location of views for fingerprinting for relayout. + * + * @hide + */ + public static final String DEVICE_CONFIG_ENABLE_RELATIVE_LOCATION_FOR_RELAYOUT = + "enable_relative_location_for_relayout"; + + /** * Bugfix flag, Autofill should only fill in value from current session. * * See frameworks/base/services/autofill/bugfixes.aconfig#fill_fields_from_current_session_only @@ -543,6 +558,22 @@ public class AutofillFeatureFlags { false); } + /** @hide */ + public static boolean enableRelayoutFixes() { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_AUTOFILL, + DEVICE_CONFIG_ENABLE_RELAYOUT, + true); + } + + /** @hide */ + public static boolean enableRelativeLocationForRelayout() { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_AUTOFILL, + DEVICE_CONFIG_ENABLE_RELATIVE_LOCATION_FOR_RELAYOUT, + false); + } + /** @hide **/ public static boolean shouldFillFieldsFromCurrentSessionOnly() { return DeviceConfig.getBoolean( diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 0d4c5560837c..8f349fecc0e6 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -748,7 +748,16 @@ public final class AutofillManager { // Controls logic around apps changing some properties of their views when activity loses // focus due to autofill showing biometric activity, password manager, or password breach check. - private boolean mRelayoutFix; + // Deprecated. TODO: Remove it after ramp of new solution. + private boolean mRelayoutFixDeprecated; + + // Controls logic around apps changing some properties of their views when activity loses + // focus due to autofill showing biometric activity, password manager, or password breach check. + private final boolean mRelayoutFix; + + // Controls logic around apps changing some properties of their views when activity loses + // focus due to autofill showing biometric activity, password manager, or password breach check. + private final boolean mRelativePositionForRelayout; // Indicates whether the credman integration is enabled. private final boolean mIsCredmanIntegrationEnabled; @@ -978,11 +987,31 @@ public final class AutofillManager { mShouldIncludeInvisibleViewInAssistStructure = AutofillFeatureFlags.shouldIncludeInvisibleViewInAssistStructure(); - mRelayoutFix = AutofillFeatureFlags.shouldIgnoreRelayoutWhenAuthPending(); + mRelayoutFixDeprecated = AutofillFeatureFlags.shouldIgnoreRelayoutWhenAuthPending(); + mRelayoutFix = AutofillFeatureFlags.enableRelayoutFixes(); + mRelativePositionForRelayout = AutofillFeatureFlags.enableRelativeLocationForRelayout(); mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration(); } /** + * Whether to apply relayout fixes. + * + * @hide + */ + public boolean isRelayoutFixEnabled() { + return mRelayoutFix; + } + + /** + * Whether to use relative positions and locations of the views for disambiguation. + * + * @hide + */ + public boolean isRelativePositionForRelayoutEnabled() { + return mRelativePositionForRelayout; + } + + /** * Whether to apply heuristic check on important views before triggering fill request * * @hide @@ -1779,7 +1808,7 @@ public final class AutofillManager { } return; } - if (mRelayoutFix && mState == STATE_PENDING_AUTHENTICATION) { + if (mRelayoutFixDeprecated && mState == STATE_PENDING_AUTHENTICATION) { if (sVerbose) { Log.v(TAG, "notifyViewVisibilityChanged(): ignoring in auth pending mode"); } @@ -2917,7 +2946,7 @@ public final class AutofillManager { Intent fillInIntent, boolean authenticateInline) { synchronized (mLock) { if (sessionId == mSessionId) { - if (mRelayoutFix) { + if (mRelayoutFixDeprecated) { mState = STATE_PENDING_AUTHENTICATION; } final AutofillClient client = getClient(); @@ -3778,7 +3807,7 @@ public final class AutofillManager { @GuardedBy("mLock") private boolean isPendingAuthenticationLocked() { - return mRelayoutFix && mState == STATE_PENDING_AUTHENTICATION; + return mRelayoutFixDeprecated && mState == STATE_PENDING_AUTHENTICATION; } @GuardedBy("mLock") diff --git a/core/java/android/view/autofill/OWNERS b/core/java/android/view/autofill/OWNERS index 898947adcd1b..7f3b4e5a21b3 100644 --- a/core/java/android/view/autofill/OWNERS +++ b/core/java/android/view/autofill/OWNERS @@ -1,10 +1,11 @@ # Bug component: 351486 -simranjit@google.com haoranzhang@google.com +jiewenlei@google.com +simranjit@google.com skxu@google.com +shuc@google.com yunicorn@google.com -reemabajwa@google.com # Bug component: 543785 = per-file *Augmented* per-file *Augmented* = wangqi@google.com diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig index 4d4e4afb621c..f570a9a50ebf 100644 --- a/core/java/android/view/flags/view_flags.aconfig +++ b/core/java/android/view/flags/view_flags.aconfig @@ -89,4 +89,12 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "disable_draw_wake_lock" + namespace: "wear_frameworks" + description: "Disable Draw Wakelock starting U." + bug: "331698645" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 21d61845edb0..9512347b0143 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -62,6 +62,7 @@ import android.content.res.TypedArray; import android.content.res.loader.ResourcesLoader; import android.content.res.loader.ResourcesProvider; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.BlendMode; import android.graphics.Outline; import android.graphics.PorterDuff; @@ -90,6 +91,7 @@ import android.util.DisplayMetrics; import android.util.IntArray; import android.util.Log; import android.util.LongArray; +import android.util.LongSparseArray; import android.util.Pair; import android.util.SizeF; import android.util.SparseArray; @@ -98,6 +100,7 @@ import android.util.TypedValue; import android.util.TypedValue.ComplexDimensionUnit; import android.util.proto.ProtoInputStream; import android.util.proto.ProtoOutputStream; +import android.util.proto.ProtoStream; import android.util.proto.ProtoUtils; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; @@ -1266,11 +1269,16 @@ public class RemoteViews implements Parcelable, Filter { int intentId = in.readInt(); String intentUri = in.readString8(); RemoteCollectionItems items = new RemoteCollectionItems(in, currentRootData); - mIdToUriMapping.put(intentId, intentUri); - mUriToCollectionMapping.put(intentUri, items); + addMapping(intentId, intentUri, items); } } + void addMapping(int intentId, String intentUri, RemoteCollectionItems items) { + mIdToUriMapping.put(intentId, intentUri); + mUriToCollectionMapping.put(intentUri, items); + } + + void setHierarchyDataForId(int intentId, HierarchyRootData data) { String uri = mIdToUriMapping.get(intentId); if (mUriToCollectionMapping.get(uri) == null) { @@ -1465,6 +1473,87 @@ public class RemoteViews implements Parcelable, Filter { mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true); } } + + public void writeToProto(Context context, ProtoOutputStream out) { + final long token = out.start(RemoteViewsProto.REMOTE_COLLECTION_CACHE); + for (int i = 0; i < mIdToUriMapping.size(); i++) { + final long entryToken = out.start(RemoteViewsProto.RemoteCollectionCache.ENTRIES); + out.write(RemoteViewsProto.RemoteCollectionCache.Entry.ID, + mIdToUriMapping.keyAt(i)); + String intentUri = mIdToUriMapping.valueAt(i); + out.write(RemoteViewsProto.RemoteCollectionCache.Entry.URI, intentUri); + final long itemsToken = out.start( + RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS); + mUriToCollectionMapping.get(intentUri).writeToProto(context, out, /* attached= */ + true); + out.end(itemsToken); + out.end(entryToken); + } + out.end(token); + } + } + + private PendingResources<RemoteCollectionCache> populateRemoteCollectionCacheFromProto( + ProtoInputStream in) throws Exception { + final ArrayList<LongSparseArray<Object>> entries = new ArrayList<>(); + final long token = in.start(RemoteViewsProto.REMOTE_COLLECTION_CACHE); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.RemoteCollectionCache.ENTRIES: + final LongSparseArray<Object> entry = new LongSparseArray<>(); + final long entryToken = in.start( + RemoteViewsProto.RemoteCollectionCache.ENTRIES); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.RemoteCollectionCache.Entry.ID: + entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.ID, + in.readInt( + RemoteViewsProto.RemoteCollectionCache.Entry.ID)); + break; + case (int) RemoteViewsProto.RemoteCollectionCache.Entry.URI: + entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.URI, + in.readString( + RemoteViewsProto.RemoteCollectionCache.Entry.URI)); + break; + case (int) RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS: + final long itemsToken = in.start( + RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS); + entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS, + RemoteCollectionItems.createFromProto(in)); + in.end(itemsToken); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(entryToken); + checkContainsKeys(entry, + new long[]{RemoteViewsProto.RemoteCollectionCache.Entry.ID, + RemoteViewsProto.RemoteCollectionCache.Entry.URI, + RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS}); + entries.add(entry); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + return (context, resources, rootData, depth) -> { + for (LongSparseArray<Object> entry : entries) { + int id = (int) entry.get(RemoteViewsProto.RemoteCollectionCache.Entry.ID); + String uri = (String) entry.get(RemoteViewsProto.RemoteCollectionCache.Entry.URI); + // Depth resets to 0 for RemoteCollectionItems + RemoteCollectionItems items = ((PendingResources<RemoteCollectionItems>) entry.get( + RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS)).create(context, + resources, rootData, depth); + rootData.mRemoteCollectionCache.addMapping(id, uri, items); + } + // Redundant return, but type signature requires we return something. + return rootData.mRemoteCollectionCache; + }; } private class SetRemoteViewsAdapterIntent extends Action { @@ -2080,6 +2169,15 @@ public class RemoteViews implements Parcelable, Filter { dest.writeTypedList(mBitmaps, flags); } + public void writeBitmapsToProto(ProtoOutputStream out) { + for (int i = 0; i < mBitmaps.size(); i++) { + final Bitmap bitmap = mBitmaps.get(i); + final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, bytes); + out.write(RemoteViewsProto.BITMAP_CACHE, bytes.toByteArray()); + } + } + public int getBitmapMemory() { if (mBitmapMemory < 0) { mBitmapMemory = 0; @@ -7522,6 +7620,127 @@ public class RemoteViews implements Parcelable, Filter { dest.restoreAllowSquashing(prevAllowSquashing); } + /** @hide */ + public void writeToProto(Context context, ProtoOutputStream out) { + writeToProto(context, out, /* attached= */ false); + } + + private void writeToProto(Context context, ProtoOutputStream out, boolean attached) { + for (long id : mIds) { + out.write(RemoteViewsProto.RemoteCollectionItems.IDS, id); + } + + boolean restoreRoot = false; + out.write(RemoteViewsProto.RemoteCollectionItems.ATTACHED, attached); + if (!attached && mViews.length > 0 && !mViews[0].mIsRoot) { + restoreRoot = true; + mViews[0].mIsRoot = true; + } + for (RemoteViews view : mViews) { + final long viewsToken = out.start(RemoteViewsProto.RemoteCollectionItems.VIEWS); + view.writePreviewToProto(context, out); + out.end(viewsToken); + } + if (restoreRoot) mViews[0].mIsRoot = false; + out.write(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS, mHasStableIds); + out.write(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT, mViewTypeCount); + } + + /** + * Overload used for testing unattached RemoteCollectionItems serialization. + * + * @hide + */ + public static RemoteCollectionItems createFromProto(Context context, ProtoInputStream in) + throws Exception { + return createFromProto(in).create(context, context.getResources(), /* rootData= */ + null, 0); + } + + /** @hide */ + public static PendingResources<RemoteCollectionItems> createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray<Object> values = new LongSparseArray<>(); + values.put(RemoteViewsProto.RemoteCollectionItems.IDS, new ArrayList<Long>()); + values.put(RemoteViewsProto.RemoteCollectionItems.VIEWS, + new ArrayList<PendingResources<RemoteViews>>()); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.RemoteCollectionItems.IDS: + ((ArrayList<Long>) values.get( + RemoteViewsProto.RemoteCollectionItems.IDS)).add( + in.readLong(RemoteViewsProto.RemoteCollectionItems.IDS)); + break; + case (int) RemoteViewsProto.RemoteCollectionItems.VIEWS: + final long viewsToken = in.start( + RemoteViewsProto.RemoteCollectionItems.VIEWS); + ((ArrayList<PendingResources<RemoteViews>>) values.get( + RemoteViewsProto.RemoteCollectionItems.VIEWS)).add( + RemoteViews.createFromProto(in)); + in.end(viewsToken); + break; + case (int) RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS: + values.put(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS, + in.readBoolean( + RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS)); + break; + case (int) RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT: + values.put(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT, + in.readInt(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT)); + break; + case (int) RemoteViewsProto.RemoteCollectionItems.ATTACHED: + values.put(RemoteViewsProto.RemoteCollectionItems.ATTACHED, + in.readBoolean(RemoteViewsProto.RemoteCollectionItems.ATTACHED)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + + checkContainsKeys(values, + new long[]{RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT}); + return (context, resources, rootData, depth) -> { + List<Long> idList = (List<Long>) values.get( + RemoteViewsProto.RemoteCollectionItems.IDS); + long[] ids = new long[idList.size()]; + for (int i = 0; i < idList.size(); i++) { + ids[i] = idList.get(i); + } + boolean attached = (boolean) values.get( + RemoteViewsProto.RemoteCollectionItems.ATTACHED, false); + List<PendingResources<RemoteViews>> pendingViews = + (List<PendingResources<RemoteViews>>) values.get( + RemoteViewsProto.RemoteCollectionItems.VIEWS); + RemoteViews[] views = new RemoteViews[pendingViews.size()]; + + if (attached && rootData == null) { + throw new IllegalStateException("Cannot create a RemoteCollectionItems from " + + "proto that was attached without providing HierarchyRootData"); + } + + int firstChildIndex = 0; + if (!attached && pendingViews.size() > 0) { + // If written as unattached, get HierarchyRootData from first view + views[0] = pendingViews.get(0).create(context, resources, /* rootData= */ null, + /* depth= */ 0); + rootData = views[0].getHierarchyRootData(); + firstChildIndex = 1; + } + for (int i = firstChildIndex; i < views.length; i++) { + // Depth is reset to 0 for RemoteCollectionItems item views, see Parcel + // constructor. + views[i] = pendingViews.get(i).create(context, resources, rootData, + /* depth= */ 0); + } + return new RemoteCollectionItems(ids, views, + (boolean) values.get(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS, + false), + (int) values.get(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT, + 0)); + }; + } + /** * Returns the id for {@code position}. See {@link #hasStableIds()} for whether this id * should be considered meaningful across collection updates. @@ -7907,6 +8126,10 @@ public class RemoteViews implements Parcelable, Filter { if (mViewId != 0 && mViewId != -1) { out.write(RemoteViewsProto.VIEW_ID, appResources.getResourceName(mViewId)); } + if (mIsRoot) { + mBitmapCache.writeBitmapsToProto(out); + mCollectionCache.writeToProto(context, out); + } out.write(RemoteViewsProto.IS_ROOT, mIsRoot); out.write(RemoteViewsProto.APPLY_FLAGS, mApplyFlags); out.write(RemoteViewsProto.HAS_DRAW_INSTRUCTIONS, mHasDrawInstructions); @@ -7968,6 +8191,7 @@ public class RemoteViews implements Parcelable, Filter { final List<PendingResources<RemoteViews>> mSizedRemoteViews = new ArrayList<>(); PendingResources<RemoteViews> mLandscapeViews = null; PendingResources<RemoteViews> mPortraitViews = null; + PendingResources<RemoteCollectionCache> mPopulateRemoteCollectionCache = null; boolean mIsRoot = false; boolean mHasDrawInstructions = false; }; @@ -8018,6 +8242,18 @@ public class RemoteViews implements Parcelable, Filter { ref.mPortraitViews = createFromProto(in); in.end(portraitToken); break; + case (int) RemoteViewsProto.BITMAP_CACHE: + byte[] src = in.readBytes(RemoteViewsProto.BITMAP_CACHE); + Bitmap bitmap = BitmapFactory.decodeByteArray(src, 0, src.length); + ref.mRv.mBitmapCache.getBitmapId(bitmap); + break; + case (int) RemoteViewsProto.REMOTE_COLLECTION_CACHE: + final long collectionToken = in.start( + RemoteViewsProto.REMOTE_COLLECTION_CACHE); + ref.mPopulateRemoteCollectionCache = + ref.mRv.populateRemoteCollectionCacheFromProto(in); + in.end(collectionToken); + break; case (int) RemoteViewsProto.IS_ROOT: ref.mIsRoot = in.readBoolean(RemoteViewsProto.IS_ROOT); break; @@ -8087,6 +8323,9 @@ public class RemoteViews implements Parcelable, Filter { rv.setLightBackgroundLayoutId(lightBackgroundLayoutId); } } + if (ref.mPopulateRemoteCollectionCache != null) { + ref.mPopulateRemoteCollectionCache.create(context, resources, rootData, depth); + } if (ref.mProviderInstanceId != -1) { rv.mProviderInstanceId = ref.mProviderInstanceId; } @@ -8139,6 +8378,16 @@ public class RemoteViews implements Parcelable, Filter { } } + private static void checkContainsKeys(LongSparseArray<?> array, long[] requiredFields) { + for (long requiredField : requiredFields) { + if (array.indexOfKey(requiredField) < 0) { + throw new IllegalArgumentException( + "RemoteViews proto missing field: " + ProtoStream.getFieldIdString( + requiredField)); + } + } + } + private static SizeF createSizeFFromProto(ProtoInputStream in) throws Exception { float width = 0; float height = 0; diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 4fb6e698d592..9b87e2351e3f 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -489,7 +489,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return; } OnBackAnimationCallback animationCallback = getBackAnimationCallback(); - if (animationCallback != null) { + if (animationCallback != null + && !(callback instanceof ImeBackAnimationController)) { mProgressAnimator.onBackInvoked(callback::onBackInvoked); } else { mProgressAnimator.reset(); diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig index 69cac6f3dfa5..94f6503f8a97 100644 --- a/core/java/android/window/flags/responsible_apis.aconfig +++ b/core/java/android/window/flags/responsible_apis.aconfig @@ -56,3 +56,11 @@ flag { description: "Improved metrics." bug: "339245692" } + +flag { + name: "bal_send_intent_with_options" + namespace: "responsible_apis" + description: "Add options parameter to IntentSender.sendIntent." + bug: "339720406" +} + diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 8cd2a3ed124e..68e33c61f71f 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -181,3 +181,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "windowing_sdk" + name: "per_user_display_window_settings" + description: "Whether to store display window settings per user to avoid conflicts" + bug: "346668297" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java index 006849034fbd..9f5ed65fa252 100644 --- a/core/java/com/android/internal/display/BrightnessSynchronizer.java +++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java @@ -78,9 +78,9 @@ public class BrightnessSynchronizer { // Feature flag that will eventually be removed private final boolean mIntRangeUserPerceptionEnabled; - public BrightnessSynchronizer(Context context, boolean intRangeUserPerceptionEnabled) { - this(context, Looper.getMainLooper(), SystemClock::uptimeMillis, - intRangeUserPerceptionEnabled); + public BrightnessSynchronizer(Context context, Looper looper, + boolean intRangeUserPerceptionEnabled) { + this(context, looper, SystemClock::uptimeMillis, intRangeUserPerceptionEnabled); } @VisibleForTesting diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java index 618f622cb9ce..ab0485175665 100644 --- a/core/java/com/android/internal/jank/Cuj.java +++ b/core/java/com/android/internal/jank/Cuj.java @@ -160,8 +160,20 @@ public class Cuj { */ public static final int CUJ_DESKTOP_MODE_RESIZE_WINDOW = 106; + /** Track entering desktop mode interaction. */ + public static final int CUJ_DESKTOP_MODE_ENTER_MODE = 107; + + /** Track exiting desktop mode interaction. */ + public static final int CUJ_DESKTOP_MODE_EXIT_MODE = 108; + + /** Track minimize window interaction in desktop mode. */ + public static final int CUJ_DESKTOP_MODE_MINIMIZE_WINDOW = 109; + + /** Track window drag interaction in desktop mode. */ + public static final int CUJ_DESKTOP_MODE_DRAG_WINDOW = 110; + // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. - @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_RESIZE_WINDOW; + @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_DRAG_WINDOW; /** @hide */ @IntDef({ @@ -259,7 +271,11 @@ public class Cuj { CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK, CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, CUJ_FOLD_ANIM, - CUJ_DESKTOP_MODE_RESIZE_WINDOW + CUJ_DESKTOP_MODE_RESIZE_WINDOW, + CUJ_DESKTOP_MODE_ENTER_MODE, + CUJ_DESKTOP_MODE_EXIT_MODE, + CUJ_DESKTOP_MODE_MINIMIZE_WINDOW, + CUJ_DESKTOP_MODE_DRAG_WINDOW }) @Retention(RetentionPolicy.SOURCE) public @interface CujType {} @@ -368,6 +384,10 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MAXIMIZE_WINDOW; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_FOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__FOLD_ANIM; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_RESIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_RESIZE_WINDOW; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_DRAG_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_DRAG_WINDOW; } private Cuj() { @@ -576,6 +596,14 @@ public class Cuj { return "FOLD_ANIM"; case CUJ_DESKTOP_MODE_RESIZE_WINDOW: return "DESKTOP_MODE_RESIZE_WINDOW"; + case CUJ_DESKTOP_MODE_ENTER_MODE: + return "DESKTOP_MODE_ENTER_MODE"; + case CUJ_DESKTOP_MODE_EXIT_MODE: + return "DESKTOP_MODE_EXIT_MODE"; + case CUJ_DESKTOP_MODE_MINIMIZE_WINDOW: + return "DESKTOP_MODE_MINIMIZE_WINDOW"; + case CUJ_DESKTOP_MODE_DRAG_WINDOW: + return "DESKTOP_MODE_DRAG_WINDOW"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 86729f74cd85..bf5df031e3ef 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -63,8 +63,8 @@ import java.util.concurrent.TimeUnit; * A class that allows the app to get the frame metrics from HardwareRendererObserver. * @hide */ -public class FrameTracker extends SurfaceControl.OnJankDataListener - implements HardwareRendererObserver.OnFrameMetricsAvailableListener { +public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvailableListener, + SurfaceControl.OnJankDataListener { private static final String TAG = "FrameTracker"; private static final long INVALID_ID = -1; @@ -118,6 +118,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener public final boolean mSurfaceOnly; private SurfaceControl mSurfaceControl; + private SurfaceControl.OnJankDataListenerRegistration mJankDataListenerRegistration; private long mBeginVsyncId = INVALID_ID; private long mEndVsyncId = INVALID_ID; private boolean mMetricsFinalized; @@ -316,7 +317,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, name, name, (int) mBeginVsyncId); markEvent("FT#beginVsync", mBeginVsyncId); markEvent("FT#layerId", mSurfaceControl.getLayerId()); - mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); + mJankDataListenerRegistration = + mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); if (!mSurfaceOnly) { mRendererWrapper.addObserver(mObserver); } @@ -342,6 +344,10 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener markEvent("FT#endVsync", mEndVsyncId); Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, name, (int) mBeginVsyncId); + if (mJankDataListenerRegistration != null) { + mJankDataListenerRegistration.removeAfter(mEndVsyncId); + } + // We don't remove observer here, // will remove it when all the frame metrics in this duration are called back. // See onFrameMetricsAvailable for the logic of removing the observer. @@ -358,6 +364,9 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener // Send a flush jank data transaction. if (mSurfaceControl != null && mSurfaceControl.isValid()) { SurfaceControl.Transaction.sendSurfaceFlushJankData(mSurfaceControl); + if (mJankDataListenerRegistration != null) { + mJankDataListenerRegistration.flush(); + } } long delay; @@ -680,7 +689,10 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener @VisibleForTesting @UiThread public void removeObservers() { - mSurfaceControlWrapper.removeJankStatsListener(this); + if (mJankDataListenerRegistration != null) { + mJankDataListenerRegistration.release(); + mJankDataListenerRegistration = null; + } if (!mSurfaceOnly) { // HWUI part. mRendererWrapper.removeObserver(mObserver); @@ -796,14 +808,10 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } public static class SurfaceControlWrapper { - - public void addJankStatsListener(SurfaceControl.OnJankDataListener listener, - SurfaceControl surfaceControl) { - SurfaceControl.addJankDataListener(listener, surfaceControl); - } - - public void removeJankStatsListener(SurfaceControl.OnJankDataListener listener) { - SurfaceControl.removeJankDataListener(listener); + /** adds the jank listener to the given surface */ + public SurfaceControl.OnJankDataListenerRegistration addJankStatsListener( + SurfaceControl.OnJankDataListener listener, SurfaceControl surfaceControl) { + return surfaceControl.addJankDataListener(listener); } } diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 66b2a9c8a424..238e6f56153b 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -75,10 +75,11 @@ import java.util.List; /** @hide */ public class TransitionAnimation { public static final int WALLPAPER_TRANSITION_NONE = 0; - public static final int WALLPAPER_TRANSITION_OPEN = 1; - public static final int WALLPAPER_TRANSITION_CLOSE = 2; - public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 3; - public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 4; + public static final int WALLPAPER_TRANSITION_CHANGE = 1; + public static final int WALLPAPER_TRANSITION_OPEN = 2; + public static final int WALLPAPER_TRANSITION_CLOSE = 3; + public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 4; + public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 5; // These are the possible states for the enter/exit activities during a thumbnail transition private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0; diff --git a/core/java/com/android/internal/protolog/common/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java index 8149cd5bbd3b..13014bfd7f3b 100644 --- a/core/java/com/android/internal/protolog/common/ProtoLog.java +++ b/core/java/com/android/internal/protolog/ProtoLog.java @@ -14,7 +14,11 @@ * limitations under the License. */ -package com.android.internal.protolog.common; +package com.android.internal.protolog; + +import com.android.internal.protolog.common.IProtoLog; +import com.android.internal.protolog.common.IProtoLogGroup; +import com.android.internal.protolog.common.LogLevel; /** * ProtoLog API - exposes static logging methods. Usage of this API is similar diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index 2316f4cd9628..b8fd3d065d8d 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -17,9 +17,12 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "Camera-JNI" +#include <android/content/AttributionSourceState.h> +#include <android_os_Parcel.h> #include <android_runtime/android_graphics_SurfaceTexture.h> #include <android_runtime/android_view_Surface.h> #include <binder/IMemory.h> +#include <binder/Parcel.h> #include <camera/Camera.h> #include <camera/StringUtils.h> #include <cutils/properties.h> @@ -523,22 +526,45 @@ void JNICameraContext::clearCallbackBuffers_l(JNIEnv *env, Vector<jbyteArray> *b } } -static jint android_hardware_Camera_getNumberOfCameras(JNIEnv *env, jobject thiz, jint deviceId, +static bool attributionSourceStateForJavaParcel(JNIEnv *env, jobject jClientAttributionParcel, + AttributionSourceState &clientAttribution) { + const Parcel *clientAttributionParcel = parcelForJavaObject(env, jClientAttributionParcel); + if (clientAttribution.readFromParcel(clientAttributionParcel) != ::android::OK) { + jniThrowRuntimeException(env, "Fail to unparcel AttributionSourceState"); + return false; + } + clientAttribution.uid = Camera::USE_CALLING_UID; + clientAttribution.pid = Camera::USE_CALLING_PID; + return true; +} + +static jint android_hardware_Camera_getNumberOfCameras(JNIEnv *env, jobject thiz, + jobject jClientAttributionParcel, jint devicePolicy) { - return Camera::getNumberOfCameras(deviceId, devicePolicy); + AttributionSourceState clientAttribution; + if (!attributionSourceStateForJavaParcel(env, jClientAttributionParcel, clientAttribution)) { + return 0; + } + return Camera::getNumberOfCameras(clientAttribution, devicePolicy); } static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jint cameraId, - jint rotationOverride, jint deviceId, + jint rotationOverride, + jobject jClientAttributionParcel, jint devicePolicy, jobject info_obj) { + AttributionSourceState clientAttribution; + if (!attributionSourceStateForJavaParcel(env, jClientAttributionParcel, clientAttribution)) { + return; + } + CameraInfo cameraInfo; - if (cameraId >= Camera::getNumberOfCameras(deviceId, devicePolicy) || cameraId < 0) { + if (cameraId >= Camera::getNumberOfCameras(clientAttribution, devicePolicy) || cameraId < 0) { ALOGE("%s: Unknown camera ID %d", __FUNCTION__, cameraId); jniThrowRuntimeException(env, "Unknown camera ID"); return; } - status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, deviceId, devicePolicy, + status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, clientAttribution, devicePolicy, &cameraInfo); if (rc != NO_ERROR) { jniThrowRuntimeException(env, "Fail to get camera info"); @@ -557,9 +583,14 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jin // connect to camera service static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, jint cameraId, jstring clientPackageName, - jint rotationOverride, - jboolean forceSlowJpegMode, jint deviceId, + jint rotationOverride, jboolean forceSlowJpegMode, + jobject jClientAttributionParcel, jint devicePolicy) { + AttributionSourceState clientAttribution; + if (!attributionSourceStateForJavaParcel(env, jClientAttributionParcel, clientAttribution)) { + return -EACCES; + } + // Convert jstring to String16 const char16_t *rawClientName = reinterpret_cast<const char16_t*>( env->GetStringChars(clientPackageName, NULL)); @@ -569,10 +600,8 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobj reinterpret_cast<const jchar*>(rawClientName)); int targetSdkVersion = android_get_application_target_sdk_version(); - sp<Camera> camera = - Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID, - targetSdkVersion, rotationOverride, forceSlowJpegMode, deviceId, - devicePolicy); + sp<Camera> camera = Camera::connect(cameraId, clientName, targetSdkVersion, rotationOverride, + forceSlowJpegMode, clientAttribution, devicePolicy); if (camera == NULL) { return -EACCES; } @@ -600,7 +629,7 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobj // Update default display orientation in case the sensor is reverse-landscape CameraInfo cameraInfo; - status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, deviceId, devicePolicy, + status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, clientAttribution, devicePolicy, &cameraInfo); if (rc != NO_ERROR) { ALOGE("%s: getCameraInfo error: %d", __FUNCTION__, rc); @@ -1056,10 +1085,11 @@ static int32_t android_hardware_Camera_getAudioRestriction( //------------------------------------------------- static const JNINativeMethod camMethods[] = { - {"_getNumberOfCameras", "(II)I", (void *)android_hardware_Camera_getNumberOfCameras}, - {"_getCameraInfo", "(IIIILandroid/hardware/Camera$CameraInfo;)V", + {"_getNumberOfCameras", "(Landroid/os/Parcel;I)I", + (void *)android_hardware_Camera_getNumberOfCameras}, + {"_getCameraInfo", "(IILandroid/os/Parcel;ILandroid/hardware/Camera$CameraInfo;)V", (void *)android_hardware_Camera_getCameraInfo}, - {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;IZII)I", + {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;IZLandroid/os/Parcel;I)I", (void *)android_hardware_Camera_native_setup}, {"native_release", "()V", (void *)android_hardware_Camera_release}, {"setPreviewSurface", "(Landroid/view/Surface;)V", diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 536583840d8f..9ce76583517b 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -22,6 +22,7 @@ #include <android/graphics/properties.h> #include <android/graphics/region.h> #include <android/gui/BnWindowInfosReportedListener.h> +#include <android/gui/JankData.h> #include <android/hardware/display/IDeviceProductInfoConstants.h> #include <android/os/IInputConstants.h> #include <android_runtime/AndroidRuntime.h> @@ -2062,11 +2063,13 @@ public: env->DeleteWeakGlobalRef(mOnJankDataListenerWeak); } - void onJankDataAvailable(const std::vector<JankData>& jankData) { + bool onJankDataAvailable(const std::vector<gui::JankData>& jankData) override { JNIEnv* env = getEnv(); jobject target = env->NewLocalRef(mOnJankDataListenerWeak); - if (target == nullptr) return; + if (target == nullptr) { + return false; + } jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(), gJankDataClassInfo.clazz, nullptr); @@ -2082,6 +2085,8 @@ public: jJankDataArray); env->DeleteLocalRef(jJankDataArray); env->DeleteLocalRef(target); + + return true; } private: @@ -2096,29 +2101,49 @@ private: jobject mOnJankDataListenerWeak; }; -static void nativeAddJankDataListener(JNIEnv* env, jclass clazz, - jlong jankDataCallbackListenerPtr, - jlong nativeSurfaceControl) { +static jlong nativeCreateJankDataListenerWrapper(JNIEnv* env, jclass clazz, + jlong nativeSurfaceControl, jobject listener) { sp<SurfaceControl> surface(reinterpret_cast<SurfaceControl *>(nativeSurfaceControl)); if (surface == nullptr) { + return 0; + } + + sp<JankDataListenerWrapper> wrapper = sp<JankDataListenerWrapper>::make(env, listener); + if (wrapper->addListener(std::move(surface)) != OK) { + return 0; + } + + wrapper->incStrong((void*)nativeCreateJankDataListenerWrapper); + return reinterpret_cast<jlong>(wrapper.get()); +} + +static void destroyJankDatalistenerWrapper(void* ptr) { + JankDataListenerWrapper* wrapper = reinterpret_cast<JankDataListenerWrapper*>(ptr); + if (wrapper == nullptr) { return; } - sp<JankDataListenerWrapper> wrapper = - reinterpret_cast<JankDataListenerWrapper*>(jankDataCallbackListenerPtr); - TransactionCompletedListener::getInstance()->addJankListener(wrapper, surface); + wrapper->decStrong((void*)nativeCreateJankDataListenerWrapper); } -static void nativeRemoveJankDataListener(JNIEnv* env, jclass clazz, - jlong jankDataCallbackListenerPtr) { - sp<JankDataListenerWrapper> wrapper = - reinterpret_cast<JankDataListenerWrapper*>(jankDataCallbackListenerPtr); - TransactionCompletedListener::getInstance()->removeJankListener(wrapper); +static jlong nativeGetJankDataListenerWrapperFinalizer() { + return reinterpret_cast<jlong>(&destroyJankDatalistenerWrapper); } -static jlong nativeCreateJankDataListenerWrapper(JNIEnv* env, jclass clazz, - jobject jankDataListenerObject) { - return reinterpret_cast<jlong>( - new JankDataListenerWrapper(env, jankDataListenerObject)); +static void nativeFlushJankData(JNIEnv* env, jclass clazz, jlong listener) { + sp<JankDataListenerWrapper> wrapper = reinterpret_cast<JankDataListenerWrapper*>(listener); + if (wrapper == nullptr) { + return; + } + wrapper->flushJankData(); +} + +static void nativeRemoveJankDataListener(JNIEnv* env, jclass clazz, jlong listener, + jlong afterVsync) { + sp<JankDataListenerWrapper> wrapper = reinterpret_cast<JankDataListenerWrapper*>(listener); + if (wrapper == nullptr) { + return; + } + wrapper->removeListener(afterVsync); } static jint nativeGetGPUContextPriority(JNIEnv* env, jclass clazz) { @@ -2436,12 +2461,14 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeRemoveCurrentInputFocus}, {"nativeSetFrameTimelineVsync", "(JJ)V", (void*)nativeSetFrameTimelineVsync }, - {"nativeAddJankDataListener", "(JJ)V", - (void*)nativeAddJankDataListener }, - {"nativeRemoveJankDataListener", "(J)V", + {"nativeFlushJankData", "(J)V", + (void*)nativeFlushJankData }, + {"nativeRemoveJankDataListener", "(JJ)V", (void*)nativeRemoveJankDataListener }, - {"nativeCreateJankDataListenerWrapper", "(Landroid/view/SurfaceControl$OnJankDataListener;)J", + {"nativeCreateJankDataListenerWrapper", "(JLandroid/view/SurfaceControl$OnJankDataListener;)J", (void*)nativeCreateJankDataListenerWrapper }, + {"nativeGetJankDataListenerWrapperFinalizer", "()J", + (void*)nativeGetJankDataListenerWrapperFinalizer }, {"nativeGetGPUContextPriority", "()I", (void*)nativeGetGPUContextPriority }, {"nativeSetTransformHint", "(JI)V", diff --git a/core/proto/android/content/res/color_state_list.proto b/core/proto/android/content/res/color_state_list.proto new file mode 100644 index 000000000000..3d0d8a887525 --- /dev/null +++ b/core/proto/android/content/res/color_state_list.proto @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless optional 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. + */ + +syntax = "proto2"; + +option java_multiple_files = true; + +package android.content.res; + +import "frameworks/base/core/proto/android/privacy.proto"; + +/** + * An android.content.res.ColorStateList object. + */ +message ColorStateListProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + repeated StateSpec state_specs = 1; + repeated int32 colors = 2 [packed = true]; + + message StateSpec { + repeated int32 state = 1 [packed = true]; + } +} diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto index d24da0362e46..f08ea1b6f092 100644 --- a/core/proto/android/widget/remoteviews.proto +++ b/core/proto/android/widget/remoteviews.proto @@ -51,6 +51,26 @@ message RemoteViewsProto { optional RemoteViewsProto landscape_remoteviews = 11; optional bool is_root = 12; optional bool has_draw_instructions = 13; + repeated bytes bitmap_cache = 14; + optional RemoteCollectionCache remote_collection_cache = 15; + + message RemoteCollectionCache { + message Entry { + optional int64 id = 1; + optional string uri = 2; + optional RemoteCollectionItems items = 3; + } + + repeated Entry entries = 1; + } + + message RemoteCollectionItems { + repeated int64 ids = 1 [packed = true]; + repeated RemoteViewsProto views = 2; + optional bool has_stable_ids = 3; + optional int32 view_type_count = 4; + optional bool attached = 5; + } } diff --git a/core/res/res/anim/overlay_task_fragment_change.xml b/core/res/res/anim/overlay_task_fragment_change.xml new file mode 100644 index 000000000000..eb02ba84d6c1 --- /dev/null +++ b/core/res/res/anim/overlay_task_fragment_change.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:showBackdrop="false"> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/overlay_task_fragment_close_to_bottom.xml b/core/res/res/anim/overlay_task_fragment_close_to_bottom.xml new file mode 100644 index 000000000000..d9487cba3265 --- /dev/null +++ b/core/res/res/anim/overlay_task_fragment_close_to_bottom.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate + android:fromYDelta="0" + android:toYDelta="100%" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:duration="517" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/overlay_task_fragment_close_to_left.xml b/core/res/res/anim/overlay_task_fragment_close_to_left.xml new file mode 100644 index 000000000000..3cdb77a307d4 --- /dev/null +++ b/core/res/res/anim/overlay_task_fragment_close_to_left.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate + android:fromXDelta="0" + android:toXDelta="-100%" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:duration="517" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/overlay_task_fragment_close_to_right.xml b/core/res/res/anim/overlay_task_fragment_close_to_right.xml new file mode 100644 index 000000000000..37645610796e --- /dev/null +++ b/core/res/res/anim/overlay_task_fragment_close_to_right.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate + android:fromXDelta="0" + android:toXDelta="100%" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:duration="517" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/overlay_task_fragment_close_to_top.xml b/core/res/res/anim/overlay_task_fragment_close_to_top.xml new file mode 100644 index 000000000000..a8bfbbdd0e78 --- /dev/null +++ b/core/res/res/anim/overlay_task_fragment_close_to_top.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate + android:fromYDelta="0" + android:toYDelta="-100%" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:duration="517" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/overlay_task_fragment_open_from_bottom.xml b/core/res/res/anim/overlay_task_fragment_open_from_bottom.xml new file mode 100644 index 000000000000..1d1223f8ead3 --- /dev/null +++ b/core/res/res/anim/overlay_task_fragment_open_from_bottom.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate + android:fromYDelta="100%" + android:toYDelta="0" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:duration="517" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/overlay_task_fragment_open_from_left.xml b/core/res/res/anim/overlay_task_fragment_open_from_left.xml new file mode 100644 index 000000000000..5e5e080c5fee --- /dev/null +++ b/core/res/res/anim/overlay_task_fragment_open_from_left.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate + android:fromXDelta="-100%" + android:toXDelta="0" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:duration="517" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/overlay_task_fragment_open_from_right.xml b/core/res/res/anim/overlay_task_fragment_open_from_right.xml new file mode 100644 index 000000000000..5674ff3199bc --- /dev/null +++ b/core/res/res/anim/overlay_task_fragment_open_from_right.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate + android:fromXDelta="100%" + android:toXDelta="0" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:duration="517" /> +</set>
\ No newline at end of file diff --git a/core/res/res/anim/overlay_task_fragment_open_from_top.xml b/core/res/res/anim/overlay_task_fragment_open_from_top.xml new file mode 100644 index 000000000000..2e3dd0a61031 --- /dev/null +++ b/core/res/res/anim/overlay_task_fragment_open_from_top.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate + android:fromYDelta="-100%" + android:toYDelta="0" + android:interpolator="@interpolator/fast_out_extra_slow_in" + android:duration="517" /> +</set>
\ No newline at end of file diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 37412a0e12b8..f5bb554b0b32 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -480,17 +480,17 @@ <!-- Colors used in Android system, from design system. These values can be overlaid at runtime by OverlayManager RROs. --> - <color name="system_primary_container_light">#5E73A9</color> - <color name="system_on_primary_container_light">#FFFFFF</color> - <color name="system_primary_light">#2A4174</color> + <color name="system_primary_container_light">#D9E2FF</color> + <color name="system_on_primary_container_light">#001945</color> + <color name="system_primary_light">#475D92</color> <color name="system_on_primary_light">#FFFFFF</color> - <color name="system_secondary_container_light">#6E7488</color> - <color name="system_on_secondary_container_light">#FFFFFF</color> - <color name="system_secondary_light">#3C4255</color> + <color name="system_secondary_container_light">#DCE2F9</color> + <color name="system_on_secondary_container_light">#151B2C</color> + <color name="system_secondary_light">#575E71</color> <color name="system_on_secondary_light">#FFFFFF</color> - <color name="system_tertiary_container_light">#8A6A89</color> - <color name="system_on_tertiary_container_light">#FFFFFF</color> - <color name="system_tertiary_light">#553A55</color> + <color name="system_tertiary_container_light">#FDD7FA</color> + <color name="system_on_tertiary_container_light">#2A122C</color> + <color name="system_tertiary_light">#725572</color> <color name="system_on_tertiary_light">#FFFFFF</color> <color name="system_background_light">#FAF8FF</color> <color name="system_on_background_light">#1A1B20</color> @@ -504,17 +504,17 @@ <color name="system_surface_bright_light">#FAF8FF</color> <color name="system_surface_dim_light">#DAD9E0</color> <color name="system_surface_variant_light">#E1E2EC</color> - <color name="system_on_surface_variant_light">#40434B</color> - <color name="system_outline_light">#5D5F67</color> - <color name="system_outline_variant_light">#797A83</color> - <color name="system_error_light">#8C0009</color> + <color name="system_on_surface_variant_light">#44464F</color> + <color name="system_outline_light">#757780</color> + <color name="system_outline_variant_light">#C5C6D0</color> + <color name="system_error_light">#BA1A1A</color> <color name="system_on_error_light">#FFFFFF</color> - <color name="system_error_container_light">#DA342E</color> - <color name="system_on_error_container_light">#FFFFFF</color> + <color name="system_error_container_light">#FFDAD6</color> + <color name="system_on_error_container_light">#410002</color> <color name="system_control_activated_light">#D9E2FF</color> <color name="system_control_normal_light">#44464F</color> <color name="system_control_highlight_light">#000000</color> - <color name="system_text_primary_inverse_light">#E2E2E9</color> +<color name="system_text_primary_inverse_light">#E2E2E9</color> <color name="system_text_secondary_and_tertiary_inverse_light">#C5C6D0</color> <color name="system_text_primary_inverse_disable_only_light">#E2E2E9</color> <color name="system_text_secondary_and_tertiary_inverse_disabled_light">#E2E2E9</color> @@ -524,22 +524,22 @@ <color name="system_palette_key_color_tertiary_light">#8C6D8C</color> <color name="system_palette_key_color_neutral_light">#76777D</color> <color name="system_palette_key_color_neutral_variant_light">#757780</color> - <color name="system_primary_container_dark">#7A90C8</color> - <color name="system_on_primary_container_dark">#000000</color> - <color name="system_primary_dark">#B7CAFF</color> - <color name="system_on_primary_dark">#00143B</color> - <color name="system_secondary_container_dark">#8A90A5</color> - <color name="system_on_secondary_container_dark">#000000</color> - <color name="system_secondary_dark">#C4CAE1</color> - <color name="system_on_secondary_dark">#0F1626</color> - <color name="system_tertiary_container_dark">#A886A6</color> - <color name="system_on_tertiary_container_dark">#000000</color> - <color name="system_tertiary_dark">#E4BFE2</color> - <color name="system_on_tertiary_dark">#240D26</color> + <color name="system_primary_container_dark">#2F4578</color> + <color name="system_on_primary_container_dark">#D9E2FF</color> + <color name="system_primary_dark">#B0C6FF</color> + <color name="system_on_primary_dark">#152E60</color> + <color name="system_secondary_container_dark">#404659</color> + <color name="system_on_secondary_container_dark">#DCE2F9</color> + <color name="system_secondary_dark">#C0C6DC</color> + <color name="system_on_secondary_dark">#2A3042</color> + <color name="system_tertiary_container_dark">#593D59</color> + <color name="system_on_tertiary_container_dark">#FDD7FA</color> + <color name="system_tertiary_dark">#E0BBDD</color> + <color name="system_on_tertiary_dark">#412742</color> <color name="system_background_dark">#121318</color> <color name="system_on_background_dark">#E2E2E9</color> <color name="system_surface_dark">#121318</color> - <color name="system_on_surface_dark">#FCFAFF</color> + <color name="system_on_surface_dark">#E2E2E9</color> <color name="system_surface_container_low_dark">#1A1B20</color> <color name="system_surface_container_lowest_dark">#0C0E13</color> <color name="system_surface_container_dark">#1E1F25</color> @@ -548,13 +548,13 @@ <color name="system_surface_bright_dark">#38393F</color> <color name="system_surface_dim_dark">#121318</color> <color name="system_surface_variant_dark">#44464F</color> - <color name="system_on_surface_variant_dark">#C9CAD4</color> - <color name="system_outline_dark">#A1A2AC</color> - <color name="system_outline_variant_dark">#81838C</color> - <color name="system_error_dark">#FFBAB1</color> - <color name="system_on_error_dark">#370001</color> - <color name="system_error_container_dark">#FF5449</color> - <color name="system_on_error_container_dark">#000000</color> + <color name="system_on_surface_variant_dark">#C5C6D0</color> + <color name="system_outline_dark">#8F9099</color> + <color name="system_outline_variant_dark">#44464F</color> + <color name="system_error_dark">#FFB4AB</color> + <color name="system_on_error_dark">#690005</color> + <color name="system_error_container_dark">#93000A</color> + <color name="system_on_error_container_dark">#FFDAD6</color> <color name="system_control_activated_dark">#2F4578</color> <color name="system_control_normal_dark">#C5C6D0</color> <color name="system_control_highlight_dark">#FFFFFF</color> @@ -568,63 +568,63 @@ <color name="system_palette_key_color_tertiary_dark">#8C6D8C</color> <color name="system_palette_key_color_neutral_dark">#76777D</color> <color name="system_palette_key_color_neutral_variant_dark">#757780</color> - <color name="system_primary_fixed">#5E73A9</color> - <color name="system_primary_fixed_dim">#455B8F</color> - <color name="system_on_primary_fixed">#FFFFFF</color> - <color name="system_on_primary_fixed_variant">#FFFFFF</color> - <color name="system_secondary_fixed">#6E7488</color> - <color name="system_secondary_fixed_dim">#555C6F</color> - <color name="system_on_secondary_fixed">#FFFFFF</color> - <color name="system_on_secondary_fixed_variant">#FFFFFF</color> - <color name="system_tertiary_fixed">#8A6A89</color> - <color name="system_tertiary_fixed_dim">#705270</color> - <color name="system_on_tertiary_fixed">#FFFFFF</color> - <color name="system_on_tertiary_fixed_variant">#FFFFFF</color> + <color name="system_primary_fixed">#D9E2FF</color> + <color name="system_primary_fixed_dim">#B0C6FF</color> + <color name="system_on_primary_fixed">#001945</color> + <color name="system_on_primary_fixed_variant">#2F4578</color> + <color name="system_secondary_fixed">#DCE2F9</color> + <color name="system_secondary_fixed_dim">#C0C6DC</color> + <color name="system_on_secondary_fixed">#151B2C</color> + <color name="system_on_secondary_fixed_variant">#404659</color> + <color name="system_tertiary_fixed">#FDD7FA</color> + <color name="system_tertiary_fixed_dim">#E0BBDD</color> + <color name="system_on_tertiary_fixed">#2A122C</color> + <color name="system_on_tertiary_fixed_variant">#593D59</color> <!--Colors used in Android system, from design system. These values can be overlaid at runtime by OverlayManager RROs.--> <color name="system_widget_background_light">#EEF0FF</color> - <color name="system_clock_hour_light">#1D2435</color> - <color name="system_clock_minute_light">#20386A</color> - <color name="system_clock_second_light">#000000</color> - <color name="system_theme_app_light">#2F4578</color> - <color name="system_on_theme_app_light">#D6DFFF</color> + <color name="system_clock_hour_light">#373D50</color> + <color name="system_clock_minute_light">#3D5487</color> + <color name="system_clock_second_light">#4F659A</color> + <color name="system_theme_app_light">#D9E2FF</color> + <color name="system_on_theme_app_light">#475D92</color> <color name="system_theme_app_ring_light">#94AAE4</color> - <color name="system_theme_notif_light">#FDD7FA</color> - <color name="system_brand_a_light">#3A5084</color> + <color name="system_theme_notif_light">#E0BBDD</color> + <color name="system_brand_a_light">#475D92</color> <color name="system_brand_b_light">#6E7488</color> - <color name="system_brand_c_light">#6076AC</color> - <color name="system_brand_d_light">#8C6D8C</color> + <color name="system_brand_c_light">#5E73A9</color> + <color name="system_brand_d_light">#8A6A89</color> <color name="system_under_surface_light">#000000</color> - <color name="system_shade_active_light">#D9E2FF</color> +<color name="system_shade_active_light">#D9E2FF</color> <color name="system_on_shade_active_light">#152E60</color> <color name="system_on_shade_active_variant_light">#2F4578</color> <color name="system_shade_inactive_light">#2F3036</color> <color name="system_on_shade_inactive_light">#E1E2EC</color> <color name="system_on_shade_inactive_variant_light">#C5C6D0</color> <color name="system_shade_disabled_light">#0C0E13</color> - <color name="system_overview_background_light">#50525A</color> + <color name="system_overview_background_light">#C5C6D0</color> <color name="system_widget_background_dark">#152E60</color> - <color name="system_clock_hour_dark">#9AA0B6</color> - <color name="system_clock_minute_dark">#D8E1FF</color> - <color name="system_clock_second_dark">#FFFFFF</color> - <color name="system_theme_app_dark">#D9E2FF</color> - <color name="system_on_theme_app_dark">#304679</color> + <color name="system_clock_hour_dark">#8A90A5</color> + <color name="system_clock_minute_dark">#D9E2FF</color> + <color name="system_clock_second_dark">#B0C6FF</color> + <color name="system_theme_app_dark">#2F4578</color> + <color name="system_on_theme_app_dark">#B0C6FF</color> <color name="system_theme_app_ring_dark">#94AAE4</color> - <color name="system_theme_notif_dark">#E0BBDD</color> - <color name="system_brand_a_dark">#90A6DF</color> - <color name="system_brand_b_dark">#A4ABC1</color> + <color name="system_theme_notif_dark">#FDD7FA</color> + <color name="system_brand_a_dark">#B0C6FF</color> + <color name="system_brand_b_dark">#DCE2F9</color> <color name="system_brand_c_dark">#7A90C8</color> - <color name="system_brand_d_dark">#A886A6</color> + <color name="system_brand_d_dark">#FDD7FA</color> <color name="system_under_surface_dark">#000000</color> - <color name="system_shade_active_dark">#D9E2FF</color> +<color name="system_shade_active_dark">#D9E2FF</color> <color name="system_on_shade_active_dark">#001945</color> <color name="system_on_shade_active_variant_dark">#2F4578</color> <color name="system_shade_inactive_dark">#2F3036</color> <color name="system_on_shade_inactive_dark">#E1E2EC</color> <color name="system_on_shade_inactive_variant_dark">#C5C6D0</color> <color name="system_shade_disabled_dark">#0C0E13</color> - <color name="system_overview_background_dark">#C5C6D0</color> + <color name="system_overview_background_dark">#50525A</color> <!-- Accessibility shortcut icon background color --> <color name="accessibility_feature_background">#5F6368</color> <!-- Google grey 700 --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index fa93e763c45b..6b71f97e3f17 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6508,17 +6508,17 @@ ul.</string> <!-- Fingerprint dangling notification title --> <string name="fingerprint_dangling_notification_title">Set up Fingerprint Unlock again</string> <!-- Fingerprint dangling notification content for only 1 fingerprint deleted --> - <string name="fingerprint_dangling_notification_msg_1"><xliff:g id="fingerprint">%s</xliff:g> wasn\'t working well and was deleted</string> + <string name="fingerprint_dangling_notification_msg_1"><xliff:g id="fingerprint">%s</xliff:g> can no longer be recognized.</string> <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted --> - <string name="fingerprint_dangling_notification_msg_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted</string> + <string name="fingerprint_dangling_notification_msg_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> can no longer be recognized.</string> <!-- Fingerprint dangling notification content for only 1 fingerprint deleted and no fingerprint left--> - <string name="fingerprint_dangling_notification_msg_all_deleted_1"><xliff:g id="fingerprint">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint.</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_1"><xliff:g id="fingerprint">%s</xliff:g> can no longer be recognized. Set up Fingerprint Unlock again.</string> <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted and no fingerprint left --> - <string name="fingerprint_dangling_notification_msg_all_deleted_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint.</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> can no longer be recognized. Set up Fingerprint Unlock again.</string> <!-- Face dangling notification title --> <string name="face_dangling_notification_title">Set up Face Unlock again</string> <!-- Face dangling notification content --> - <string name="face_dangling_notification_msg">Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face.</string> + <string name="face_dangling_notification_msg">Your face model can no longer be recognized. Set up Face Unlock again.</string> <!-- Biometric dangling notification "set up" action button --> <string name="biometric_dangling_notification_action_set_up">Set up</string> <!-- Biometric dangling notification "Not now" action button --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index bdcf13c24798..7d50d227ee13 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1753,6 +1753,15 @@ <java-symbol type="anim" name="task_fragment_clear_top_close_exit" /> <java-symbol type="anim" name="task_fragment_clear_top_open_enter" /> <java-symbol type="anim" name="task_fragment_clear_top_open_exit" /> + <java-symbol type="anim" name="overlay_task_fragment_open_from_left" /> + <java-symbol type="anim" name="overlay_task_fragment_open_from_top" /> + <java-symbol type="anim" name="overlay_task_fragment_open_from_right" /> + <java-symbol type="anim" name="overlay_task_fragment_open_from_bottom" /> + <java-symbol type="anim" name="overlay_task_fragment_change" /> + <java-symbol type="anim" name="overlay_task_fragment_close_to_left" /> + <java-symbol type="anim" name="overlay_task_fragment_close_to_top" /> + <java-symbol type="anim" name="overlay_task_fragment_close_to_right" /> + <java-symbol type="anim" name="overlay_task_fragment_close_to_bottom" /> <java-symbol type="array" name="config_autoRotationTiltTolerance" /> <java-symbol type="array" name="config_longPressVibePattern" /> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 67cceb5d5343..581dee571a69 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -185,6 +185,9 @@ https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf --> <shortcode country="it" pattern="\\d{5}" premium="44[0-4]\\d{2}|47[0-4]\\d{2}|48[0-4]\\d{2}|44[5-9]\\d{4}|47[5-9]\\d{4}|48[5-9]\\d{4}|455\\d{2}|499\\d{2}" free="116\\d{3}|4112503|40\\d{0,12}" standard="430\\d{2}|431\\d{2}|434\\d{4}|435\\d{4}|439\\d{7}" /> + <!-- Jordan: 1-5 digits (standard system default, not country specific) --> + <shortcode country="jo" pattern="\\d{1,5}" free="99066" /> + <!-- Japan: 8083 used by SOFTBANK_DCB_2 --> <shortcode country="jp" pattern="\\d{1,5}" free="8083" /> diff --git a/core/tests/coretests/src/android/graphics/ColorStateListTest.java b/core/tests/coretests/src/android/graphics/ColorStateListTest.java index a3d52eab1682..ab41bd07ac6d 100644 --- a/core/tests/coretests/src/android/graphics/ColorStateListTest.java +++ b/core/tests/coretests/src/android/graphics/ColorStateListTest.java @@ -19,6 +19,8 @@ package android.graphics; import android.content.res.ColorStateList; import android.content.res.Resources; import android.test.AndroidTestCase; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; import androidx.test.filters.SmallTest; @@ -49,6 +51,15 @@ public class ColorStateListTest extends AndroidTestCase { } @SmallTest + public void testStateIsInList_proto() throws Exception { + ColorStateList colorStateList = recreateFromProto( + mResources.getColorStateList(R.color.color1)); + int[] focusedState = {android.R.attr.state_focused}; + int focusColor = colorStateList.getColorForState(focusedState, R.color.failColor); + assertEquals(mResources.getColor(R.color.testcolor1), focusColor); + } + + @SmallTest public void testEmptyState() throws Exception { ColorStateList colorStateList = mResources.getColorStateList(R.color.color1); int[] emptyState = {}; @@ -57,6 +68,15 @@ public class ColorStateListTest extends AndroidTestCase { } @SmallTest + public void testEmptyState_proto() throws Exception { + ColorStateList colorStateList = recreateFromProto( + mResources.getColorStateList(R.color.color1)); + int[] emptyState = {}; + int defaultColor = colorStateList.getColorForState(emptyState, mFailureColor); + assertEquals(mResources.getColor(R.color.testcolor2), defaultColor); + } + + @SmallTest public void testGetColor() throws Exception { int defaultColor = mResources.getColor(R.color.color1); assertEquals(mResources.getColor(R.color.testcolor2), defaultColor); @@ -73,4 +93,11 @@ public class ColorStateListTest extends AndroidTestCase { int defaultColor = mResources.getColor(R.color.color_with_lstar); assertEquals(mResources.getColor(R.color.testcolor3), defaultColor); } + + private ColorStateList recreateFromProto(ColorStateList colorStateList) throws Exception { + ProtoOutputStream out = new ProtoOutputStream(); + colorStateList.writeToProto(out); + ProtoInputStream in = new ProtoInputStream(out.getBytes()); + return ColorStateList.createFromProto(in); + } } diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java index 58e5be2b823d..4d9b591c0990 100644 --- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java +++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java @@ -34,6 +34,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; +import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Insets; import android.platform.test.annotations.Presubmit; @@ -102,6 +103,8 @@ public class ImeBackAnimationControllerTest { mViewRoot.setOnContentApplyWindowInsetsListener( mock(Window.OnContentApplyWindowInsetsListener.class)); mBackAnimationController = new ImeBackAnimationController(mViewRoot, mInsetsController); + mViewRoot.mContext.getResources().getConfiguration().windowConfiguration + .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); when(mWindowInsetsAnimationController.getHiddenStateInsets()).thenReturn(Insets.NONE); when(mWindowInsetsAnimationController.getShownStateInsets()).thenReturn(IME_INSETS); @@ -156,8 +159,28 @@ public class ImeBackAnimationControllerTest { mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, 0.5f, EDGE_LEFT)); // commit back gesture mBackAnimationController.onBackInvoked(); - // verify that InsetsController#hide is called - verify(mInsetsController, times(1)).hide(ime()); + // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever + // getInputMethodManager is called from ImeBackAnimationController) + verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager(); + // verify that ImeBackAnimationController does not take control over IME insets + verify(mInsetsController, never()).controlWindowInsetsAnimation(anyInt(), any(), any(), + anyBoolean(), anyLong(), any(), anyInt(), anyBoolean()); + } + + @Test + public void testMultiWindowModeNotPlayingAnim() { + // setup ViewRoot with WINDOWING_MODE_MULTI_WINDOW + mViewRoot.mContext.getResources().getConfiguration().windowConfiguration.setWindowingMode( + WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); + // start back gesture + mBackAnimationController.onBackStarted(new BackEvent(0f, 0f, 0f, EDGE_LEFT)); + // progress back gesture + mBackAnimationController.onBackProgressed(new BackEvent(100f, 0f, 0.5f, EDGE_LEFT)); + // commit back gesture + mBackAnimationController.onBackInvoked(); + // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever + // getInputMethodManager is called from ImeBackAnimationController) + verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager(); // verify that ImeBackAnimationController does not take control over IME insets verify(mInsetsController, never()).controlWindowInsetsAnimation(anyInt(), any(), any(), anyBoolean(), anyLong(), any(), anyInt(), anyBoolean()); @@ -277,9 +300,9 @@ public class ImeBackAnimationControllerTest { // commit back gesture mBackAnimationController.onBackInvoked(); - - // verify that InsetsController#hide is called - verify(mInsetsController, times(1)).hide(ime()); + // verify that InputMethodManager#notifyImeHidden is called (which is the case whenever + // getInputMethodManager is called from ImeBackAnimationController) + verify(mViewRootInsetsControllerHost, times(1)).getInputMethodManager(); }); } diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java index 1a7117e3b4a1..499caf5e12d3 100644 --- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java @@ -29,6 +29,7 @@ import static com.android.internal.util.FrameworkStatsLog.UI_INTERACTION_FRAME_I import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -83,6 +84,7 @@ public class FrameTrackerTest { private ChoreographerWrapper mChoreographer; private StatsLogWrapper mStatsLog; private ArgumentCaptor<OnJankDataListener> mListenerCapture; + private SurfaceControl.OnJankDataListenerRegistration mJankStatsRegistration; private SurfaceControl mSurfaceControl; private FrameTracker.FrameTrackerListener mTrackerListener; private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; @@ -107,10 +109,11 @@ public class FrameTrackerTest { mSurfaceControlWrapper = mock(SurfaceControlWrapper.class); mListenerCapture = ArgumentCaptor.forClass(OnJankDataListener.class); - doNothing().when(mSurfaceControlWrapper).addJankStatsListener( + mJankStatsRegistration = mock(SurfaceControl.OnJankDataListenerRegistration.class); + doReturn(mJankStatsRegistration).when(mSurfaceControlWrapper).addJankStatsListener( mListenerCapture.capture(), any()); - doNothing().when(mSurfaceControlWrapper).removeJankStatsListener( - mListenerCapture.capture()); + doNothing().when(mJankStatsRegistration).flush(); + doNothing().when(mJankStatsRegistration).removeAfter(anyLong()); mChoreographer = mock(ChoreographerWrapper.class); mStatsLog = mock(StatsLogWrapper.class); @@ -483,7 +486,7 @@ public class FrameTrackerTest { // an extra frame to trigger finish sendFrame(tracker, JANK_NONE, 103L); - verify(mSurfaceControlWrapper).removeJankStatsListener(any()); + verify(mJankStatsRegistration).removeAfter(anyLong()); verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), @@ -520,7 +523,7 @@ public class FrameTrackerTest { // an extra frame to trigger finish sendFrame(tracker, JANK_NONE, 103L); - verify(mSurfaceControlWrapper).removeJankStatsListener(any()); + verify(mJankStatsRegistration).removeAfter(anyLong()); verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), @@ -557,7 +560,7 @@ public class FrameTrackerTest { // janky frame, should be ignored, trigger finish sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L); - verify(mSurfaceControlWrapper).removeJankStatsListener(any()); + verify(mJankStatsRegistration).removeAfter(anyLong()); verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), @@ -589,7 +592,7 @@ public class FrameTrackerTest { tracker.end(FrameTracker.REASON_END_NORMAL); sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 106L); sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 107L); - verify(mSurfaceControlWrapper).removeJankStatsListener(any()); + verify(mJankStatsRegistration).removeAfter(anyLong()); verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index 68095e5eb46c..f76398465378 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -183,8 +183,6 @@ public class InteractionJankMonitorTest { doNothing().when(viewRoot).removeSurfaceChangedCallback(any()); SurfaceControlWrapper surfaceControl = mock(SurfaceControlWrapper.class); - doNothing().when(surfaceControl).addJankStatsListener(any(), any()); - doNothing().when(surfaceControl).removeJankStatsListener(any()); final ChoreographerWrapper choreographer = mock(ChoreographerWrapper.class); doReturn(SystemClock.elapsedRealtime()).when(choreographer).getVsyncId(); diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java index 06d888b59166..7ffc7b218eed 100644 --- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java +++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java @@ -18,7 +18,6 @@ package com.android.internal.os; import static com.google.common.truth.Truth.assertThat; -import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; @@ -77,7 +76,6 @@ public class MonotonicClockTest { } @Test - @IgnoreUnderRavenwood(reason = "b/321832617") public void corruptedFile() throws IOException { // Create an invalid binary XML file to cause IOException: "Unexpected magic number" try (FileWriter w = new FileWriter(mFile)) { diff --git a/data/keyboards/Vendor_18d1_Product_4f60.idc b/data/keyboards/Vendor_18d1_Product_4f60.idc new file mode 100644 index 000000000000..b9fd406d0b93 --- /dev/null +++ b/data/keyboards/Vendor_18d1_Product_4f60.idc @@ -0,0 +1,18 @@ +# Copyright 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Increase palm thresholds, since this touchpad has a tendency to overstate +# touch sizes. +gestureProp.Palm_Width = 40.0 +gestureProp.Multiple_Palm_Width = 40.0 diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index d0e49d8c403f..eb1fc23d6b00 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -20,8 +20,10 @@ import static android.content.pm.PackageManager.MATCH_ALL; import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider; import static androidx.window.extensions.embedding.SplitAttributesHelper.isReversedLayout; +import static androidx.window.extensions.embedding.SplitController.TAG; import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK; +import android.annotation.AnimRes; import android.app.Activity; import android.app.ActivityThread; import android.app.WindowConfiguration; @@ -31,9 +33,11 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; +import android.util.Log; import android.util.Pair; import android.util.Size; import android.view.View; @@ -56,6 +60,7 @@ import androidx.window.extensions.layout.FoldingFeature; import androidx.window.extensions.layout.WindowLayoutComponentImpl; import androidx.window.extensions.layout.WindowLayoutInfo; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.window.flags.Flags; @@ -125,6 +130,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { static final int RESULT_EXPAND_FAILED_NO_TF_INFO = 2; /** + * The key of {@link ActivityStack} alignment relative to its parent container. + * <p> + * See {@link ContainerPosition} for possible values. + * <p> + * Note that this constants must align with the definition in WM Jetpack library. + */ + private static final String KEY_ACTIVITY_STACK_ALIGNMENT = + "androidx.window.embedding.ActivityStackAlignment"; + + /** * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer, * Activity, Activity, Intent)} */ @@ -649,14 +664,114 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds // and WCT#setWindowingMode to take fragmentToken. resizeTaskFragmentIfRegistered(wct, container, relativeBounds); - int windowingMode = container.getTaskContainer().getWindowingModeForTaskFragment( - relativeBounds); + final TaskContainer taskContainer = container.getTaskContainer(); + final int windowingMode = taskContainer.getWindowingModeForTaskFragment(relativeBounds); updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); - // Always use default animation for standalone ActivityStack. - updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT); + if (container.isOverlay() && isOverlayTransitionSupported()) { + // Use the overlay transition for the overlay container if it's supported. + final TaskFragmentAnimationParams params = createOverlayAnimationParams(relativeBounds, + taskContainer.getBounds(), container); + updateAnimationParams(wct, fragmentToken, params); + } else { + // Otherwise, fallabck to use the default animation params. + updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT); + } setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask); } + private static boolean isOverlayTransitionSupported() { + return Flags.moveAnimationOptionsToChange() + && Flags.activityEmbeddingOverlayPresentationFlag(); + } + + @NonNull + private static TaskFragmentAnimationParams createOverlayAnimationParams( + @NonNull Rect relativeBounds, @NonNull Rect parentContainerBounds, + @NonNull TaskFragmentContainer container) { + if (relativeBounds.isEmpty()) { + return TaskFragmentAnimationParams.DEFAULT; + } + + final int positionFromOptions = container.getLaunchOptions() + .getInt(KEY_ACTIVITY_STACK_ALIGNMENT , -1); + final int position = positionFromOptions != -1 ? positionFromOptions + // Fallback to calculate from bounds if the info can't be retrieved from options. + : getOverlayPosition(relativeBounds, parentContainerBounds); + + return new TaskFragmentAnimationParams.Builder() + .setOpenAnimationResId(getOpenAnimationResourcesId(position)) + .setChangeAnimationResId(R.anim.overlay_task_fragment_change) + .setCloseAnimationResId(getCloseAnimationResourcesId(position)) + .build(); + } + + @VisibleForTesting + @ContainerPosition + static int getOverlayPosition( + @NonNull Rect relativeBounds, @NonNull Rect parentContainerBounds) { + final Rect relativeParentBounds = new Rect(parentContainerBounds); + relativeParentBounds.offsetTo(0, 0); + final int leftMatch = (relativeParentBounds.left == relativeBounds.left) ? 1 : 0; + final int topMatch = (relativeParentBounds.top == relativeBounds.top) ? 1 : 0; + final int rightMatch = (relativeParentBounds.right == relativeBounds.right) ? 1 : 0; + final int bottomMatch = (relativeParentBounds.bottom == relativeBounds.bottom) ? 1 : 0; + + // Flag format: {left|top|right|bottom}. Note that overlay container could be shrunk and + // centered, which makes only one of overlay container edge matches the parent container. + final int directionFlag = (leftMatch << 3) + (topMatch << 2) + (rightMatch << 1) + + bottomMatch; + + final int position = switch (directionFlag) { + // Only the left edge match or only the right edge not match: should be on the left of + // the parent container. + case 0b1000, 0b1101 -> CONTAINER_POSITION_LEFT; + // Only the top edge match or only the bottom edge not match: should be on the top of + // the parent container. + case 0b0100, 0b1110 -> CONTAINER_POSITION_TOP; + // Only the right edge match or only the left edge not match: should be on the right of + // the parent container. + case 0b0010, 0b0111 -> CONTAINER_POSITION_RIGHT; + // Only the bottom edge match or only the top edge not match: should be on the bottom of + // the parent container. + case 0b0001, 0b1011 -> CONTAINER_POSITION_BOTTOM; + default -> { + Log.w(TAG, "Unsupported position:" + Integer.toBinaryString(directionFlag) + + " fallback to treat it as right. Relative parent bounds: " + + relativeParentBounds + ", relative overlay bounds:" + relativeBounds); + yield CONTAINER_POSITION_RIGHT; + } + }; + return position; + } + + @AnimRes + private static int getOpenAnimationResourcesId(@ContainerPosition int position) { + return switch (position) { + case CONTAINER_POSITION_LEFT -> R.anim.overlay_task_fragment_open_from_left; + case CONTAINER_POSITION_TOP -> R.anim.overlay_task_fragment_open_from_top; + case CONTAINER_POSITION_RIGHT -> R.anim.overlay_task_fragment_open_from_right; + case CONTAINER_POSITION_BOTTOM -> R.anim.overlay_task_fragment_open_from_bottom; + default -> { + Log.w(TAG, "Unknown position:" + position); + yield Resources.ID_NULL; + } + }; + } + + @AnimRes + private static int getCloseAnimationResourcesId(@ContainerPosition int position) { + return switch (position) { + case CONTAINER_POSITION_LEFT -> R.anim.overlay_task_fragment_close_to_left; + case CONTAINER_POSITION_TOP -> R.anim.overlay_task_fragment_close_to_top; + case CONTAINER_POSITION_RIGHT -> R.anim.overlay_task_fragment_close_to_right; + case CONTAINER_POSITION_BOTTOM -> R.anim.overlay_task_fragment_close_to_bottom; + default -> { + Log.w(TAG, "Unknown position:" + position); + yield Resources.ID_NULL; + } + }; + } + /** * Returns the expanded bounds if the {@code relBounds} violate minimum dimension or are not * fully covered by the task bounds. Otherwise, returns {@code relBounds}. diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 7a0b9a0ece6b..325750243744 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -30,6 +30,11 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSpli import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer; +import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM; +import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT; +import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT; +import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP; +import static androidx.window.extensions.embedding.SplitPresenter.getOverlayPosition; import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds; import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK; @@ -666,8 +671,8 @@ public class OverlayPresentationTest { attributes.getRelativeBounds()); verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container, WINDOWING_MODE_MULTI_WINDOW); - verify(mSplitPresenter).updateAnimationParams(mTransaction, token, - TaskFragmentAnimationParams.DEFAULT); + verify(mSplitPresenter).updateAnimationParams(eq(mTransaction), eq(token), + any(TaskFragmentAnimationParams.class)); verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true); verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), any(TaskFragmentContainer.class), anyBoolean()); @@ -691,8 +696,8 @@ public class OverlayPresentationTest { attributes.getRelativeBounds()); verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container, WINDOWING_MODE_MULTI_WINDOW); - verify(mSplitPresenter).updateAnimationParams(mTransaction, token, - TaskFragmentAnimationParams.DEFAULT); + verify(mSplitPresenter).updateAnimationParams(eq(mTransaction), eq(token), + any(TaskFragmentAnimationParams.class)); verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(), any(TaskFragmentContainer.class), anyBoolean()); verify(mSplitPresenter).setTaskFragmentPinned(mTransaction, container, true); @@ -870,6 +875,59 @@ public class OverlayPresentationTest { eq(overlayContainer.getTaskFragmentToken()), eq(activityToken)); } + // TODO(b/243518738): Rewrite with TestParameter. + @Test + public void testGetOverlayPosition() { + assertWithMessage("It must be position left for left overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top, + TASK_BOUNDS.right / 2, + TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT); + assertWithMessage("It must be position left for shrunk left overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top + 20, + TASK_BOUNDS.right / 2, + TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_LEFT); + assertWithMessage("It must be position left for top overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.top, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP); + assertWithMessage("It must be position left for shrunk top overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left + 20, + TASK_BOUNDS.top, + TASK_BOUNDS.right - 20, + TASK_BOUNDS.bottom / 2), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_TOP); + assertWithMessage("It must be position left for right overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.right / 2, + TASK_BOUNDS.top, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT); + assertWithMessage("It must be position left for shrunk right overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.right / 2, + TASK_BOUNDS.top + 20, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom - 20), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_RIGHT); + assertWithMessage("It must be position left for bottom overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left, + TASK_BOUNDS.bottom / 2, + TASK_BOUNDS.right, + TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM); + assertWithMessage("It must be position left for shrunk bottom overlay.") + .that(getOverlayPosition(new Rect( + TASK_BOUNDS.left + 20, + TASK_BOUNDS.bottom / 20, + TASK_BOUNDS.right - 20, + TASK_BOUNDS.bottom), TASK_BOUNDS)).isEqualTo(CONTAINER_POSITION_BOTTOM); + } + /** * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded} */ diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 25d3067a34bc..1e6824196687 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -88,7 +88,7 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) transform-protolog-calls " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--protolog-class com.android.internal.protolog.ProtoLog " + "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " + "--loggroups-jar $(location :wm_shell_protolog-groups) " + "--viewer-config-file-path /system_ext/etc/wmshell.protolog.pb " + @@ -107,7 +107,7 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) generate-viewer-config " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--protolog-class com.android.internal.protolog.ProtoLog " + "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " + "--loggroups-jar $(location :wm_shell_protolog-groups) " + "--viewer-config-type json " + @@ -124,7 +124,7 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) generate-viewer-config " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--protolog-class com.android.internal.protolog.ProtoLog " + "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " + "--loggroups-jar $(location :wm_shell_protolog-groups) " + "--viewer-config-type proto " + diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/OWNERS b/libs/WindowManager/Shell/multivalentScreenshotTests/OWNERS new file mode 100644 index 000000000000..dc11241fb76b --- /dev/null +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/OWNERS @@ -0,0 +1,4 @@ +atsjenk@google.com +liranb@google.com +madym@google.com +mpodolian@google.com
\ No newline at end of file diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png Binary files differindex eb2888199ddf..027b28e7ace7 100644 --- a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png Binary files differindex eb2888199ddf..027b28e7ace7 100644 --- a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png +++ b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt index 9e1440d5716b..ae60d8bc2596 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -27,7 +27,7 @@ import android.view.WindowManager import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT import com.android.wm.shell.common.bubbles.BubbleBarLocation diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 327e2059557c..5e673338bad3 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -32,7 +32,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.logging.testing.UiEventLoggerFake -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.Flags import com.android.wm.shell.R diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt index ace2c131050c..935d12916f56 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt @@ -27,7 +27,7 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner import com.android.wm.shell.bubbles.DeviceConfig diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml index 7d5f9cdbebc8..5fe3f2af63a0 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml @@ -14,88 +14,100 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/maximize_menu" - style="?android:attr/buttonBarStyle" android:layout_width="@dimen/desktop_mode_maximize_menu_width" android:layout_height="@dimen/desktop_mode_maximize_menu_height" - android:orientation="horizontal" - android:gravity="center" - android:padding="16dp" android:background="@drawable/desktop_mode_maximize_menu_background" android:elevation="1dp"> <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> + android:id="@+id/container" + android:layout_width="@dimen/desktop_mode_maximize_menu_width" + android:layout_height="@dimen/desktop_mode_maximize_menu_height" + android:orientation="horizontal" + android:padding="16dp" + android:gravity="center"> - <Button - android:layout_width="94dp" - android:layout_height="60dp" - android:id="@+id/maximize_menu_maximize_button" - style="?android:attr/buttonBarButtonStyle" - android:stateListAnimator="@null" - android:layout_marginRight="8dp" - android:layout_marginBottom="4dp" - android:alpha="0"/> - - <TextView - android:id="@+id/maximize_menu_maximize_window_text" - android:layout_width="94dp" - android:layout_height="18dp" - android:textSize="11sp" - android:layout_marginBottom="76dp" - android:gravity="center" - android:fontFamily="google-sans-text" - android:text="@string/desktop_mode_maximize_menu_maximize_text" - android:textColor="?androidprv:attr/materialColorOnSurface" - android:alpha="0"/> - </LinearLayout> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> <LinearLayout - android:id="@+id/maximize_menu_snap_menu_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal" - android:padding="4dp" - android:background="@drawable/desktop_mode_maximize_menu_layout_background" - android:layout_marginBottom="4dp" - android:alpha="0"> - <Button - android:id="@+id/maximize_menu_snap_left_button" - style="?android:attr/buttonBarButtonStyle" - android:layout_width="41dp" - android:layout_height="@dimen/desktop_mode_maximize_menu_button_height" - android:layout_marginRight="4dp" - android:background="@drawable/desktop_mode_maximize_menu_button_background" - android:stateListAnimator="@null"/> + android:orientation="vertical"> <Button - android:id="@+id/maximize_menu_snap_right_button" + android:layout_width="94dp" + android:layout_height="60dp" + android:id="@+id/maximize_menu_maximize_button" style="?android:attr/buttonBarButtonStyle" - android:layout_width="41dp" - android:layout_height="@dimen/desktop_mode_maximize_menu_button_height" - android:background="@drawable/desktop_mode_maximize_menu_button_background" - android:stateListAnimator="@null"/> + android:stateListAnimator="@null" + android:layout_marginRight="8dp" + android:layout_marginBottom="4dp" + android:alpha="0"/> + + <TextView + android:id="@+id/maximize_menu_maximize_window_text" + android:layout_width="94dp" + android:layout_height="18dp" + android:textSize="11sp" + android:layout_marginBottom="76dp" + android:gravity="center" + android:fontFamily="google-sans-text" + android:text="@string/desktop_mode_maximize_menu_maximize_text" + android:textColor="?androidprv:attr/materialColorOnSurface" + android:alpha="0"/> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + <LinearLayout + android:id="@+id/maximize_menu_snap_menu_layout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="4dp" + android:background="@drawable/desktop_mode_maximize_menu_layout_background" + android:layout_marginBottom="4dp" + android:alpha="0"> + <Button + android:id="@+id/maximize_menu_snap_left_button" + style="?android:attr/buttonBarButtonStyle" + android:layout_width="41dp" + android:layout_height="@dimen/desktop_mode_maximize_menu_button_height" + android:layout_marginRight="4dp" + android:background="@drawable/desktop_mode_maximize_menu_button_background" + android:stateListAnimator="@null"/> + + <Button + android:id="@+id/maximize_menu_snap_right_button" + style="?android:attr/buttonBarButtonStyle" + android:layout_width="41dp" + android:layout_height="@dimen/desktop_mode_maximize_menu_button_height" + android:background="@drawable/desktop_mode_maximize_menu_button_background" + android:stateListAnimator="@null"/> + </LinearLayout> + <TextView + android:id="@+id/maximize_menu_snap_window_text" + android:layout_width="94dp" + android:layout_height="18dp" + android:textSize="11sp" + android:layout_marginBottom="76dp" + android:layout_gravity="center" + android:gravity="center" + android:fontFamily="google-sans-text" + android:text="@string/desktop_mode_maximize_menu_snap_text" + android:textColor="?androidprv:attr/materialColorOnSurface" + android:alpha="0"/> </LinearLayout> - <TextView - android:id="@+id/maximize_menu_snap_window_text" - android:layout_width="94dp" - android:layout_height="18dp" - android:textSize="11sp" - android:layout_marginBottom="76dp" - android:layout_gravity="center" - android:gravity="center" - android:fontFamily="google-sans-text" - android:text="@string/desktop_mode_maximize_menu_snap_text" - android:textColor="?androidprv:attr/materialColorOnSurface" - android:alpha="0"/> </LinearLayout> -</LinearLayout> + + <!-- Empty view intentionally placed in front of everything else and matching the menu size + used to monitor input events over the entire menu. --> + <View + android:id="@+id/maximize_menu_overlay" + android:layout_width="@dimen/desktop_mode_maximize_menu_width" + android:layout_height="@dimen/desktop_mode_maximize_menu_height"/> +</FrameLayout> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java index ef9bf008b294..514307fed4f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java @@ -19,7 +19,7 @@ package com.android.wm.shell; import com.android.internal.protolog.LegacyProtoLogImpl; import com.android.internal.protolog.common.ILogger; import com.android.internal.protolog.common.IProtoLog; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java index 2e5448a9e8d5..b9bf136837a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java @@ -29,7 +29,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java index 5143d419597b..9f01316d5b5c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java @@ -38,7 +38,7 @@ import android.window.SystemPerformanceHinter; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 3ded7d246499..ebdea1bba942 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -52,7 +52,7 @@ import android.window.TaskAppearedInfo; import android.window.TaskOrganizer; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; @@ -124,6 +124,15 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } /** + * Limited scope callback to notify when a task is removed from the system. This signal is + * not synchronized with anything (or any transition), and should not be used in cases where + * that is necessary. + */ + public interface TaskVanishedListener { + default void onTaskVanished(RunningTaskInfo taskInfo) {} + } + + /** * Callbacks for events on a task with a locus id. */ public interface LocusIdListener { @@ -167,6 +176,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements private final ArraySet<FocusListener> mFocusListeners = new ArraySet<>(); + // Listeners that should be notified when a task is removed + private final ArraySet<TaskVanishedListener> mTaskVanishedListeners = new ArraySet<>(); + private final Object mLock = new Object(); private StartingWindowController mStartingWindow; @@ -409,7 +421,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } /** - * Removes listener. + * Removes a locus id listener. */ public void removeLocusIdListener(LocusIdListener listener) { synchronized (mLock) { @@ -430,7 +442,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } /** - * Removes listener. + * Removes a focus listener. */ public void removeFocusListener(FocusListener listener) { synchronized (mLock) { @@ -439,6 +451,24 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } /** + * Adds a listener to be notified when a task vanishes. + */ + public void addTaskVanishedListener(TaskVanishedListener listener) { + synchronized (mLock) { + mTaskVanishedListeners.add(listener); + } + } + + /** + * Removes a task-vanished listener. + */ + public void removeTaskVanishedListener(TaskVanishedListener listener) { + synchronized (mLock) { + mTaskVanishedListeners.remove(listener); + } + } + + /** * Returns a surface which can be used to attach overlays to the home root task */ @NonNull @@ -614,6 +644,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements t.apply(); ProtoLog.v(WM_SHELL_TASK_ORG, "Removing overlay surface"); } + for (TaskVanishedListener l : mTaskVanishedListeners) { + l.onTaskVanished(taskInfo); + } if (!ENABLE_SHELL_TRANSITIONS && (appearedInfo.getLeash() != null)) { // Preemptively clean up the leash only if shell transitions are not enabled diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index 5a42817e839b..d270d2b4ccf1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -265,7 +265,7 @@ class ActivityEmbeddingAnimationRunner { for (TransitionInfo.Change change : openingChanges) { final Animation animation = animationProvider.get(info, change, openingWholeScreenBounds); - if (animation.getDuration() == 0) { + if (shouldUseJumpCutForAnimation(animation)) { continue; } final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( @@ -290,7 +290,7 @@ class ActivityEmbeddingAnimationRunner { } final Animation animation = animationProvider.get(info, change, closingWholeScreenBounds); - if (animation.getDuration() == 0) { + if (shouldUseJumpCutForAnimation(animation)) { continue; } final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter( @@ -444,8 +444,16 @@ class ActivityEmbeddingAnimationRunner { calculateParentBounds(change, boundsAnimationChange, parentBounds); // There are two animations in the array. The first one is for the start leash // (snapshot), and the second one is for the end leash (TaskFragment). - final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change, - parentBounds); + final Animation[] animations = + mAnimationSpec.createChangeBoundsChangeAnimations(info, change, parentBounds); + // Jump cut if either animation has zero for duration. + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + for (Animation animation : animations) { + if (shouldUseJumpCutForAnimation(animation)) { + return new ArrayList<>(); + } + } + } // Keep track as we might need to add background color for the animation. // Although there may be multiple change animation, record one of them is sufficient // because the background color will be added to the root leash for the whole animation. @@ -492,12 +500,19 @@ class ActivityEmbeddingAnimationRunner { // window without bounds change. animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change); } else if (TransitionUtil.isClosingType(change.getMode())) { - animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds); + animation = + mAnimationSpec.createChangeBoundsCloseAnimation(info, change, parentBounds); shouldShowBackgroundColor = false; } else { - animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds); + animation = + mAnimationSpec.createChangeBoundsOpenAnimation(info, change, parentBounds); shouldShowBackgroundColor = false; } + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + if (shouldUseJumpCutForAnimation(animation)) { + return new ArrayList<>(); + } + } adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change, TransitionUtil.getRootFor(change, info))); } @@ -640,6 +655,12 @@ class ActivityEmbeddingAnimationRunner { return true; } + /** Whether or not to use jump cut based on the animation. */ + @VisibleForTesting + static boolean shouldUseJumpCutForAnimation(@NonNull Animation animation) { + return animation.getDuration() == 0; + } + /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */ private void prepareForJumpCut(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java index 8d49614b021b..f49b90d08a75 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java @@ -46,7 +46,6 @@ import com.android.window.flags.Flags; import com.android.wm.shell.shared.TransitionUtil; /** Animation spec for ActivityEmbedding transition. */ -// TODO(b/206557124): provide an easier way to customize animation class ActivityEmbeddingAnimationSpec { private static final String TAG = "ActivityEmbeddingAnimSpec"; @@ -95,8 +94,14 @@ class ActivityEmbeddingAnimationSpec { /** Animation for window that is opening in a change transition. */ @NonNull - Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change, - @NonNull Rect parentBounds) { + Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info, + @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + final Animation customAnimation = loadCustomAnimation(info, change); + if (customAnimation != null) { + return customAnimation; + } + } // Use end bounds for opening. final Rect bounds = change.getEndAbsBounds(); final int startLeft; @@ -123,8 +128,14 @@ class ActivityEmbeddingAnimationSpec { /** Animation for window that is closing in a change transition. */ @NonNull - Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo.Change change, - @NonNull Rect parentBounds) { + Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info, + @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + final Animation customAnimation = loadCustomAnimation(info, change); + if (customAnimation != null) { + return customAnimation; + } + } // Use start bounds for closing. final Rect bounds = change.getStartAbsBounds(); final int endTop; @@ -155,8 +166,17 @@ class ActivityEmbeddingAnimationSpec { * the second one is for the end leash. */ @NonNull - Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo.Change change, - @NonNull Rect parentBounds) { + Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo info, + @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) { + if (Flags.activityEmbeddingAnimationCustomizationFlag()) { + // TODO(b/293658614): Support more complicated animations that may need more than a noop + // animation as the start leash. + final Animation noopAnimation = createNoopAnimation(change); + final Animation customAnimation = loadCustomAnimation(info, change); + if (customAnimation != null) { + return new Animation[]{noopAnimation, customAnimation}; + } + } // Both start bounds and end bounds are in screen coordinates. We will post translate // to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate final Rect startBounds = change.getStartAbsBounds(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 7041ea307b0f..ece02711070e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -59,7 +59,7 @@ import android.window.IBackAnimationRunner; import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.LatencyTracker; import com.android.internal.view.AppearanceRegion; import com.android.wm.shell.R; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index c9d3dbdcae05..4f04c5c28412 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -48,7 +48,7 @@ import com.android.internal.dynamicanimation.animation.SpringForce import com.android.internal.jank.Cuj import com.android.internal.policy.ScreenDecorationsUtils import com.android.internal.policy.SystemBarUtils -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.animation.Interpolators diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index 381914a58cf2..103a65422504 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -49,7 +49,7 @@ import android.window.IOnBackInvokedCallback; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.SystemBarUtils; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.shared.annotations.ShellMainThread; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt index c738ce542f8a..e266e2cd7eea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt @@ -28,7 +28,7 @@ import android.window.BackMotionEvent import android.window.BackNavigationInfo import com.android.internal.R import com.android.internal.policy.TransitionAnimation -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.shared.annotations.ShellMainThread diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 2aefc64a3ebb..7dbbb04e4406 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -48,7 +48,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index c853301519e9..e36f6e6c04c5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -84,7 +84,7 @@ import androidx.annotation.MainThread; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.CollectionUtils; import com.android.launcher3.icons.BubbleIconFactory; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 761e02598460..4e6c517b9194 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -35,7 +35,7 @@ import android.view.View; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubbles.DismissReason; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index c7ccd50af550..f7a5c271a729 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -67,7 +67,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.common.AlphaOptimizedButton; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java index 18e04d14c71b..bf98ef82b475 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java @@ -42,7 +42,7 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ContrastColorUtil; import com.android.wm.shell.Flags; import com.android.wm.shell.R; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 2382545ab324..0cf187bd9c0f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -29,7 +29,7 @@ import android.view.WindowManager; import androidx.annotation.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconNormalizer; import com.android.wm.shell.R; import com.android.wm.shell.common.bubbles.BubbleBarLocation; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 09bec8c37b9a..f93f19d5d1eb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -78,7 +78,7 @@ import androidx.dynamicanimation.animation.SpringForce; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.wm.shell.Flags; import com.android.wm.shell.R; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 0b66bcb6930e..c79d9c4942bf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -35,7 +35,7 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.taskview.TaskView; /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java index 137568458e3c..9429c9e71b3b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java @@ -29,7 +29,7 @@ import android.view.InputMonitor; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener; /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java index b7107f09b17f..d4f53ab353ea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java @@ -28,7 +28,7 @@ import android.view.ViewConfiguration; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; /** * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java index aa4129a14dbc..fbef6b5e4a99 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java @@ -38,7 +38,7 @@ import androidx.dynamicanimation.animation.FloatPropertyCompat; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.animation.FlingAnimationUtils; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.bubbles.BubbleExpandedView; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java index e261d92bda5c..f7923924789e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java @@ -28,7 +28,7 @@ import android.window.WindowContainerTransaction; import android.window.WindowContainerTransactionCallback; import android.window.WindowOrganizer; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.transition.LegacyTransitions; import java.util.ArrayList; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java index 43c92cab6a68..43f9cb984322 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java @@ -32,7 +32,7 @@ import android.util.ArraySet; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java index 58007b50350b..8e026f04ac31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java @@ -27,7 +27,7 @@ import android.util.DisplayMetrics; import android.util.Size; import android.view.Gravity; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 7ceaaea3962f..64a1b0c804da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -32,7 +32,7 @@ import android.util.ArraySet; import android.util.Size; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.function.TriConsumer; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java index c421dec025f2..b9c698e5d8b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipPerfHintController.java @@ -26,7 +26,7 @@ import android.window.SystemPerformanceHinter.HighPerfSession; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.shared.annotations.ShellMainThread; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index 3e9366fd6459..dcf84d927ad3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -30,7 +30,7 @@ import android.util.Log import android.util.Pair import android.util.TypedValue import android.window.TaskSnapshot -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.Flags import com.android.wm.shell.protolog.ShellProtoLogGroup import kotlin.math.abs diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index 2234041b8c9d..3ad60e7031e5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -54,7 +54,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 8ced76fd23af..d3c349f9c866 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -59,7 +59,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.animation.Interpolators; 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 1fcfa7fcf350..4ea41d5256f9 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 @@ -17,6 +17,7 @@ package com.android.wm.shell.dagger; import android.annotation.Nullable; +import android.app.KeyguardManager; import android.content.Context; import android.content.pm.LauncherApps; import android.os.Handler; @@ -514,6 +515,7 @@ public abstract class WMShellModule { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, DragAndDropController dragAndDropController, Transitions transitions, + KeyguardManager keyguardManager, EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler, ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, @@ -528,7 +530,7 @@ public abstract class WMShellModule { Optional<RecentTasksController> recentTasksController) { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, - dragAndDropController, transitions, enterDesktopTransitionHandler, + dragAndDropController, transitions, keyguardManager, enterDesktopTransitionHandler, exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, @@ -644,6 +646,7 @@ public abstract class WMShellModule { ShellInit shellInit, ShellController shellController, ShellCommandHandler shellCommandHandler, + ShellTaskOrganizer shellTaskOrganizer, DisplayController displayController, UiEventLogger uiEventLogger, IconProvider iconProvider, @@ -651,8 +654,8 @@ public abstract class WMShellModule { Transitions transitions, @ShellMainThread ShellExecutor mainExecutor) { return new DragAndDropController(context, shellInit, shellController, shellCommandHandler, - displayController, uiEventLogger, iconProvider, globalDragListener, transitions, - mainExecutor); + shellTaskOrganizer, displayController, uiEventLogger, iconProvider, + globalDragListener, transitions, mainExecutor); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 677fd5deffd3..240cf3b96e89 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -212,12 +212,13 @@ public abstract class Pip1Module { @WMSingleton @Provides static PipMotionHelper providePipMotionHelper(Context context, + @ShellMainThread ShellExecutor mainExecutor, PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm, PipTransitionController pipTransitionController, FloatingContentCoordinator floatingContentCoordinator, Optional<PipPerfHintController> pipPerfHintControllerOptional) { - return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer, + return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer, menuController, pipSnapAlgorithm, pipTransitionController, floatingContentCoordinator, pipPerfHintControllerOptional); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index e71056043d5c..a67dee3a4a8d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -35,7 +35,7 @@ import androidx.core.util.plus import androidx.core.util.putAll import com.android.internal.logging.InstanceId import com.android.internal.logging.InstanceIdSequence -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 196538248709..5813f8513b06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions +import android.app.KeyguardManager import android.app.PendingIntent import android.app.TaskInfo import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME @@ -108,6 +109,7 @@ class DesktopTasksController( private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, private val dragAndDropController: DragAndDropController, private val transitions: Transitions, + private val keyguardManager: KeyguardManager, private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler, private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler, private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, @@ -972,6 +974,12 @@ class DesktopTasksController( transition: IBinder ): WindowContainerTransaction? { KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch") + if (keyguardManager.isKeyguardLocked) { + // Do NOT handle freeform task launch when locked. + // It will be launched in fullscreen windowing mode (Details: b/160925539) + KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked") + return null + } if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) { KtProtoLog.d( WM_SHELL_DESKTOP_MODE, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index c374eb8e8f03..b3c3a3dcf272 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -59,9 +59,10 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.UiEventLogger; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; @@ -85,6 +86,7 @@ import java.util.function.Function; public class DragAndDropController implements RemoteCallable<DragAndDropController>, GlobalDragListener.GlobalDragListenerCallback, DisplayController.OnDisplaysChangedListener, + ShellTaskOrganizer.TaskVanishedListener, View.OnDragListener, ComponentCallbacks2 { private static final String TAG = DragAndDropController.class.getSimpleName(); @@ -92,6 +94,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll private final Context mContext; private final ShellController mShellController; private final ShellCommandHandler mShellCommandHandler; + private final ShellTaskOrganizer mShellTaskOrganizer; private final DisplayController mDisplayController; private final DragAndDropEventLogger mLogger; private final IconProvider mIconProvider; @@ -133,6 +136,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll ShellInit shellInit, ShellController shellController, ShellCommandHandler shellCommandHandler, + ShellTaskOrganizer shellTaskOrganizer, DisplayController displayController, UiEventLogger uiEventLogger, IconProvider iconProvider, @@ -142,6 +146,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll mContext = context; mShellController = shellController; mShellCommandHandler = shellCommandHandler; + mShellTaskOrganizer = shellTaskOrganizer; mDisplayController = displayController; mLogger = new DragAndDropEventLogger(uiEventLogger); mIconProvider = iconProvider; @@ -163,6 +168,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll }, 0); mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP, this::createExternalInterface, this); + mShellTaskOrganizer.addTaskVanishedListener(this); mShellCommandHandler.addDumpCallback(this::dump, this); mGlobalDragListener.setListener(this); } @@ -281,6 +287,34 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll } @Override + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + if (taskInfo.baseIntent == null) { + // Invalid info + return; + } + // Find the active drag + PerDisplay pd = null; + for (int i = 0; i < mDisplayDropTargets.size(); i++) { + final PerDisplay iPd = mDisplayDropTargets.valueAt(i); + if (iPd.isHandlingDrag) { + pd = iPd; + break; + } + } + if (pd == null || !pd.isHandlingDrag) { + // Not currently dragging + return; + } + + // Update the drag session + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Handling vanished task: id=%d component=%s", taskInfo.taskId, + taskInfo.baseIntent.getComponent()); + pd.dragSession.updateRunningTask(); + pd.dragLayout.updateSession(pd.dragSession); + } + + @Override public boolean onDrag(View target, DragEvent event) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f", @@ -313,11 +347,10 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll Slog.w(TAG, "Unexpected drag start during an active drag"); return false; } - // TODO(b/290391688): Also update the session data with task stack changes pd.dragSession = new DragSession(ActivityTaskManager.getInstance(), mDisplayController.getDisplayLayout(displayId), event.getClipData(), event.getDragFlags()); - pd.dragSession.update(); + pd.dragSession.initialize(); pd.activeDragCount++; pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession)); setDropTargetWindowVisibility(pd, View.VISIBLE); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index a42ca1905ee7..9c7476d1a1b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -66,7 +66,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.InstanceId; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -84,7 +84,10 @@ public class DragAndDropPolicy { private static final String TAG = DragAndDropPolicy.class.getSimpleName(); private final Context mContext; - private final Starter mStarter; + // Used only for launching a fullscreen task (or as a fallback if there is no split starter) + private final Starter mFullscreenStarter; + // Used for launching tasks into splitscreen + private final Starter mSplitscreenStarter; private final SplitScreenController mSplitScreen; private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>(); private final RectF mDisallowHitRegion = new RectF(); @@ -97,10 +100,12 @@ public class DragAndDropPolicy { } @VisibleForTesting - DragAndDropPolicy(Context context, SplitScreenController splitScreen, Starter starter) { + DragAndDropPolicy(Context context, SplitScreenController splitScreen, + Starter fullscreenStarter) { mContext = context; mSplitScreen = splitScreen; - mStarter = mSplitScreen != null ? mSplitScreen : starter; + mFullscreenStarter = fullscreenStarter; + mSplitscreenStarter = splitScreen; } /** @@ -245,17 +250,20 @@ public class DragAndDropPolicy { mSplitScreen.onDroppedToSplit(position, mLoggerSessionId); } + final Starter starter = target.type == TYPE_FULLSCREEN + ? mFullscreenStarter + : mSplitscreenStarter; if (mSession.appData != null) { - launchApp(mSession, position); + launchApp(mSession, starter, position); } else { - launchIntent(mSession, position); + launchIntent(mSession, starter, position); } } /** * Launches an app provided by SysUI. */ - private void launchApp(DragSession session, @SplitPosition int position) { + private void launchApp(DragSession session, Starter starter, @SplitPosition int position) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d", position); final ClipDescription description = session.getClipDescription(); @@ -275,11 +283,11 @@ public class DragAndDropPolicy { if (isTask) { final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID); - mStarter.startTask(taskId, position, opts); + starter.startTask(taskId, position, opts); } else if (isShortcut) { final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME); final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID); - mStarter.startShortcut(packageName, id, position, opts, user); + starter.startShortcut(packageName, id, position, opts, user); } else { final PendingIntent launchIntent = session.appData.getParcelableExtra(EXTRA_PENDING_INTENT); @@ -288,7 +296,7 @@ public class DragAndDropPolicy { Log.e(TAG, "Expected app intent's EXTRA_USER to match pending intent user"); } } - mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */, + starter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */, position, opts); } } @@ -296,7 +304,7 @@ public class DragAndDropPolicy { /** * Launches an intent sender provided by an application. */ - private void launchIntent(DragSession session, @SplitPosition int position) { + private void launchIntent(DragSession session, Starter starter, @SplitPosition int position) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d", position); final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic(); @@ -309,7 +317,7 @@ public class DragAndDropPolicy { | FLAG_ACTIVITY_MULTIPLE_TASK); final Bundle opts = baseActivityOpts.toBundle(); - mStarter.startIntent(session.launchableIntent, + starter.startIntent(session.launchableIntent, session.launchableIntent.getCreatorUserHandle().getIdentifier(), null /* fillIntent */, position, opts); } @@ -420,7 +428,7 @@ public class DragAndDropPolicy { @Override public String toString() { - return "Target {hit=" + hitRegion + " draw=" + drawRegion + "}"; + return "Target {type=" + type + " hit=" + hitRegion + " draw=" + drawRegion + "}"; } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index 4bb10dfdf8c6..910175ef3023 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -20,7 +20,6 @@ import static android.app.StatusBarManager.DISABLE_NONE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS; import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; @@ -42,6 +41,7 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Insets; +import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.graphics.drawable.Drawable; @@ -55,7 +55,7 @@ import android.widget.LinearLayout; import androidx.annotation.NonNull; import com.android.internal.logging.InstanceId; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; @@ -102,6 +102,8 @@ public class DragLayout extends LinearLayout private boolean mIsShowing; private boolean mHasDropped; private DragSession mSession; + // The last position that was handled by the drag layout + private final Point mLastPosition = new Point(); @SuppressLint("WrongConstant") public DragLayout(Context context, SplitScreenController splitScreenController, @@ -265,6 +267,15 @@ public class DragLayout extends LinearLayout */ public void prepare(DragSession session, InstanceId loggerSessionId) { mPolicy.start(session, loggerSessionId); + updateSession(session); + } + + /** + * Updates the drag layout based on the diven drag session. + */ + public void updateSession(DragSession session) { + // Note: The policy currently just keeps a reference to the session + boolean updatingExistingSession = mSession != null; mSession = session; mHasDropped = false; mCurrentTarget = null; @@ -312,6 +323,11 @@ public class DragLayout extends LinearLayout updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds); } requestLayout(); + if (updatingExistingSession) { + // Update targets if we are already currently dragging + recomputeDropTargets(); + update(mLastPosition.x, mLastPosition.y); + } } private void updateDropZoneSizesForSingleTask() { @@ -359,6 +375,9 @@ public class DragLayout extends LinearLayout mDropZoneView2.setLayoutParams(dropZoneView2); } + /** + * Shows the drag layout. + */ public void show() { mIsShowing = true; recomputeDropTargets(); @@ -384,13 +403,19 @@ public class DragLayout extends LinearLayout * Updates the visible drop target as the user drags. */ public void update(DragEvent event) { + update((int) event.getX(), (int) event.getY()); + } + + /** + * Updates the visible drop target as the user drags to the given coordinates. + */ + private void update(int x, int y) { if (mHasDropped) { return; } // Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the // visibility of the current region - DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation( - (int) event.getX(), (int) event.getY()); + DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(x, y); if (mCurrentTarget != target) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target); if (target == null) { @@ -429,6 +454,7 @@ public class DragLayout extends LinearLayout } mCurrentTarget = target; } + mLastPosition.set(x, y); } /** @@ -436,6 +462,7 @@ public class DragLayout extends LinearLayout */ public void hide(DragEvent event, Runnable hideCompleteCallback) { mIsShowing = false; + mLastPosition.set(-1, -1); animateSplitContainers(false, () -> { if (hideCompleteCallback != null) { hideCompleteCallback.run(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java index 0addd432aff0..3bedef21184e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java @@ -30,7 +30,9 @@ import android.content.pm.ActivityInfo; import androidx.annotation.Nullable; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.List; @@ -79,17 +81,27 @@ public class DragSession { } /** - * Updates the session data based on the current state of the system. + * Updates the running task for this drag session. */ - void update() { - List<ActivityManager.RunningTaskInfo> tasks = + void updateRunningTask() { + final List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */); if (!tasks.isEmpty()) { final ActivityManager.RunningTaskInfo task = tasks.get(0); runningTaskInfo = task; runningTaskWinMode = task.getWindowingMode(); runningTaskActType = task.getActivityType(); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "Running task: id=%d component=%s", task.taskId, + task.baseIntent != null ? task.baseIntent.getComponent() : "null"); } + } + + /** + * Updates the session data based on the current state of the system at the start of the drag. + */ + void initialize() { + updateRunningTask(); activityInfo = mInitialDragData.getItemAt(0).getActivityInfo(); // TODO: This should technically check & respect config_supportsNonResizableMultiWindow diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java index 724a130ef52d..b6b49a87484e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java @@ -19,6 +19,7 @@ package com.android.wm.shell.draganddrop; import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; @@ -37,13 +38,16 @@ import android.widget.ImageView; import androidx.annotation.Nullable; import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; +import com.android.wm.shell.protolog.ShellProtoLogGroup; /** * Renders a drop zone area for items being dragged. */ public class DropZoneView extends FrameLayout { + private static final boolean DEBUG_LAYOUT = false; private static final float SPLASHSCREEN_ALPHA = 0.90f; private static final float HIGHLIGHT_ALPHA = 1f; private static final int MARGIN_ANIMATION_ENTER_DURATION = 400; @@ -77,6 +81,7 @@ public class DropZoneView extends FrameLayout { private int mHighlightColor; private ObjectAnimator mBackgroundAnimator; + private int mTargetBackgroundColor; private ObjectAnimator mMarginAnimator; private float mMarginPercent; @@ -181,6 +186,9 @@ public class DropZoneView extends FrameLayout { /** Animates between highlight and splashscreen depending on current state. */ public void animateSwitch() { + if (DEBUG_LAYOUT) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "animateSwitch"); + } mShowingHighlight = !mShowingHighlight; mShowingSplash = !mShowingHighlight; final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor; @@ -190,6 +198,10 @@ public class DropZoneView extends FrameLayout { /** Animates the highlight indicating the zone is hovered on or not. */ public void setShowingHighlight(boolean showingHighlight) { + if (DEBUG_LAYOUT) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setShowingHighlight: showing=%b", + showingHighlight); + } mShowingHighlight = showingHighlight; mShowingSplash = !mShowingHighlight; final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor; @@ -199,6 +211,10 @@ public class DropZoneView extends FrameLayout { /** Animates the margins around the drop zone to show or hide. */ public void setShowingMargin(boolean visible) { + if (DEBUG_LAYOUT) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setShowingMargin: visible=%b", + visible); + } if (mShowingMargin != visible) { mShowingMargin = visible; animateMarginToState(); @@ -212,6 +228,15 @@ public class DropZoneView extends FrameLayout { } private void animateBackground(int startColor, int endColor) { + if (DEBUG_LAYOUT) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, + "animateBackground: start=%s end=%s", + Integer.toHexString(startColor), Integer.toHexString(endColor)); + } + if (endColor == mTargetBackgroundColor) { + // Already at, or animating to, that background color + return; + } if (mBackgroundAnimator != null) { mBackgroundAnimator.cancel(); } @@ -223,6 +248,7 @@ public class DropZoneView extends FrameLayout { mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN); } mBackgroundAnimator.start(); + mTargetBackgroundColor = endColor; } private void animateSplashScreenIcon() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt index 31214eba8dd0..ffcfe6447e2d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt @@ -25,7 +25,7 @@ import android.view.IWindowManager import android.window.IGlobalDragListener import android.window.IUnhandledDragCallback import androidx.annotation.VisibleForTesting -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.protolog.ShellProtoLogGroup import java.util.function.Consumer diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index b48aee5ccd5e..1641668a98a1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -25,7 +25,7 @@ import android.content.Context; import android.util.SparseArray; import android.view.SurfaceControl; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java index 2626e7380163..d2ceb67030fc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java @@ -27,7 +27,7 @@ import android.view.SurfaceControl; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index cd478e5bd567..333c75f92ffc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -49,7 +49,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java index ce98458c0575..93ede7a8b7aa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java @@ -16,7 +16,6 @@ package com.android.wm.shell.pip; -import android.content.ComponentName; import android.os.RemoteException; import android.view.IPinnedTaskListener; import android.view.WindowManagerGlobal; @@ -70,12 +69,6 @@ public class PinnedStackListenerForwarder { } } - private void onActivityHidden(ComponentName componentName) { - for (PinnedTaskListener listener : mListeners) { - listener.onActivityHidden(componentName); - } - } - @BinderThread private class PinnedTaskListenerImpl extends IPinnedTaskListener.Stub { @Override @@ -91,13 +84,6 @@ public class PinnedStackListenerForwarder { PinnedStackListenerForwarder.this.onImeVisibilityChanged(imeVisible, imeHeight); }); } - - @Override - public void onActivityHidden(ComponentName componentName) { - mMainExecutor.execute(() -> { - PinnedStackListenerForwarder.this.onActivityHidden(componentName); - }); - } } /** @@ -108,7 +94,5 @@ public class PinnedStackListenerForwarder { public void onMovementBoundsChanged(boolean fromImeAdjustment) {} public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {} - - public void onActivityHidden(ComponentName componentName) {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index a749019046f8..b27c428f1693 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -16,10 +16,12 @@ package com.android.wm.shell.pip; +import android.annotation.NonNull; import android.graphics.Rect; import com.android.wm.shell.shared.annotations.ExternalThread; +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -69,9 +71,10 @@ public interface Pip { default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { } /** - * @return {@link PipTransitionController} instance. + * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition + * started / finished callbacks. */ - default PipTransitionController getPipTransitionController() { - return null; - } + default void registerPipTransitionCallback( + @NonNull PipTransitionController.PipTransitionCallback callback, + @NonNull Executor executor) { } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 0a3c15b6057f..dc449d1aaf74 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -37,7 +37,7 @@ import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.pip.PipUtils; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index e2e1ecde8b56..789f7068c0a9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -73,7 +73,7 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.animation.Interpolators; @@ -423,7 +423,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, }); mPipTransitionController.setPipOrganizer(this); displayController.addDisplayWindowListener(this); - pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); + pipTransitionController.registerPipTransitionCallback( + mPipTransitionCallback, mMainExecutor); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 3cae72d89ecc..e5633de2a3a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -24,6 +24,7 @@ import static android.util.RotationUtils.rotateBounds; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; @@ -62,7 +63,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; @@ -300,6 +301,10 @@ public class PipTransition extends PipTransitionController { finishTransaction); } + if (isCurrentPipActivityClosed(info)) { + mPipBoundsState.setLastPipComponentName(null /* componentName */); + } + return false; } @@ -322,6 +327,21 @@ public class PipTransition extends PipTransitionController { return true; } + private boolean isCurrentPipActivityClosed(TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + boolean isTaskChange = change.getTaskInfo() != null; + boolean hasComponentNameOfPip = change.getActivityComponent() != null + && change.getActivityComponent().equals( + mPipBoundsState.getLastPipComponentName()); + if (!isTaskChange && change.getMode() == TRANSIT_CLOSE && hasComponentNameOfPip) { + return true; + } + } + return false; + } + + @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 6eefdcfc4d93..ccbe94c4c780 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -41,7 +41,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; @@ -53,8 +53,9 @@ import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; /** * Responsible supplying PiP Transitions. @@ -66,7 +67,7 @@ public abstract class PipTransitionController implements Transitions.TransitionH protected final ShellTaskOrganizer mShellTaskOrganizer; protected final PipMenuController mPipMenuController; protected final Transitions mTransitions; - private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>(); + private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>(); protected PipTaskOrganizer mPipOrganizer; protected DefaultMixedHandler mMixedHandler; @@ -181,16 +182,18 @@ public abstract class PipTransitionController implements Transitions.TransitionH /** * Registers {@link PipTransitionCallback} to receive transition callbacks. */ - public void registerPipTransitionCallback(PipTransitionCallback callback) { - mPipTransitionCallbacks.add(callback); + public void registerPipTransitionCallback( + @NonNull PipTransitionCallback callback, @NonNull Executor executor) { + mPipTransitionCallbacks.put(callback, executor); } protected void sendOnPipTransitionStarted( @PipAnimationController.TransitionDirection int direction) { final Rect pipBounds = mPipBoundsState.getBounds(); - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionStarted(direction, pipBounds); + for (Map.Entry<PipTransitionCallback, Executor> entry + : mPipTransitionCallbacks.entrySet()) { + entry.getValue().execute( + () -> entry.getKey().onPipTransitionStarted(direction, pipBounds)); } if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) { try { @@ -207,9 +210,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH protected void sendOnPipTransitionFinished( @PipAnimationController.TransitionDirection int direction) { - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionFinished(direction); + for (Map.Entry<PipTransitionCallback, Executor> entry + : mPipTransitionCallbacks.entrySet()) { + entry.getValue().execute( + () -> entry.getKey().onPipTransitionFinished(direction)); } if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) { try { @@ -226,9 +230,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH protected void sendOnPipTransitionCancelled( @PipAnimationController.TransitionDirection int direction) { - for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) { - final PipTransitionCallback callback = mPipTransitionCallbacks.get(i); - callback.onPipTransitionCanceled(direction); + for (Map.Entry<PipTransitionCallback, Executor> entry + : mPipTransitionCallbacks.entrySet()) { + entry.getValue().execute( + () -> entry.getKey().onPipTransitionCanceled(direction)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 0169e8c40f45..c7369a3cd347 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -32,7 +32,7 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.WindowManagerGlobal; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.pip.PipBoundsState; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 8c4bf7620068..d1d82755d12c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -59,7 +59,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayChangeController; @@ -106,6 +106,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -367,15 +368,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb false /* fromRotation */, fromImeAdjustment, false /* fromShelfAdjustment */, null /* windowContainerTransaction */); } - - @Override - public void onActivityHidden(ComponentName componentName) { - if (componentName.equals(mPipBoundsState.getLastPipComponentName())) { - // The activity was removed, we don't want to restore to the reentry state - // saved for this component anymore. - mPipBoundsState.setLastPipComponentName(null); - } - } } /** @@ -487,7 +479,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mShellCommandHandler.addDumpCallback(this::dump, this); mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), INPUT_CONSUMER_PIP, mMainExecutor); - mPipTransitionController.registerPipTransitionCallback(this); + mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor); mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> { mPipDisplayLayoutState.setDisplayId(displayId); onDisplayChanged(mDisplayController.getDisplayLayout(displayId), @@ -1229,8 +1221,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public PipTransitionController getPipTransitionController() { - return mPipTransitionController; + public void registerPipTransitionCallback( + PipTransitionController.PipTransitionCallback callback, + Executor executor) { + mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback( + callback, executor)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java index f6cab485fa2a..d1978c30eecb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java @@ -28,7 +28,7 @@ import android.view.IWindowManager; import android.view.InputChannel; import android.view.InputEvent; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 15342be0e8b7..c18964240f98 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -59,7 +59,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.LinearLayout; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.ShellExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index ef468434db6a..df3803d54d9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -34,10 +34,11 @@ import android.graphics.PointF; import android.graphics.Rect; import android.os.Debug; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.FloatProperties; import com.android.wm.shell.common.FloatingContentCoordinator; +import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.common.pip.PipAppOpsListener; import com.android.wm.shell.common.pip.PipBoundsState; @@ -47,6 +48,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.PhysicsAnimator; +import com.android.wm.shell.shared.annotations.ShellMainThread; import kotlin.Unit; import kotlin.jvm.functions.Function0; @@ -171,7 +173,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, public void onPipTransitionCanceled(int direction) {} }; - public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState, + public PipMotionHelper(Context context, + @ShellMainThread ShellExecutor mainExecutor, + @NonNull PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController, FloatingContentCoordinator floatingContentCoordinator, @@ -183,7 +187,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mSnapAlgorithm = snapAlgorithm; mFloatingContentCoordinator = floatingContentCoordinator; mPipPerfHintController = pipPerfHintControllerOptional.orElse(null); - pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback); + pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor); mResizePipUpdateListener = (target, values) -> { if (mPipBoundsState.getMotionBoundsState().isInMotion()) { mPipTaskOrganizer.scheduleUserResizePip(getBounds(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index d8ac8e948a97..9c4e723efc23 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -48,7 +48,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java index 5d858fa9aa3f..cb82db630715 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java @@ -23,7 +23,7 @@ import android.view.VelocityTracker; import android.view.ViewConfiguration; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java index 6b890c49b713..50d22ad00b11 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java @@ -33,7 +33,7 @@ import android.app.RemoteAction; import android.content.Context; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.pip.PipMediaController; import com.android.wm.shell.common.pip.PipUtils; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java index 0221db836dda..eb7a10cc9dfb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBackgroundView.java @@ -28,7 +28,7 @@ import android.widget.FrameLayout; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java index 72c0cd71f198..188c35ff3562 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java @@ -33,7 +33,7 @@ import android.view.Gravity; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java index 8a215b4b2e25..1afb470c5e9b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsController.java @@ -26,7 +26,7 @@ import android.graphics.Rect; import android.os.Handler; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 3d286461ef79..0ed5079b7fba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -39,7 +39,7 @@ import android.view.Gravity; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; @@ -257,7 +257,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } private void onInit() { - mPipTransitionController.registerPipTransitionCallback(this); + mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor); reloadResources(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java index 977aad4a898a..327ceef00e6a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java @@ -27,7 +27,7 @@ import android.content.Context; import android.os.Bundle; import android.os.Handler; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.TvWindowMenuActionButton; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index 6b5bdd2299e1..e74870d4d139 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -37,7 +37,7 @@ import android.window.SurfaceSyncGroup; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.pip.PipMenuController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java index adc03cf5c4d7..eabf1b0b3063 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuEduTextDrawer.java @@ -39,7 +39,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import java.util.Arrays; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index 4a767ef2a113..c7704f0b4eed 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -50,7 +50,7 @@ import android.widget.ImageView; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.widget.LinearLayoutManager; import com.android.internal.widget.RecyclerView; import com.android.wm.shell.R; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java index 54e162bba2f3..ce5079227b61 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java @@ -33,7 +33,7 @@ import android.os.Bundle; import android.text.TextUtils; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ImageUtils; import com.android.wm.shell.R; import com.android.wm.shell.common.pip.PipMediaController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java index ca0d61f8fc9b..7a0e6694cb51 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java @@ -62,7 +62,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipDisplayLayoutState; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java index 6e36a32ac931..9cfe1620a2ff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java @@ -32,7 +32,7 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.WindowManagerGlobal; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.pip.PipBoundsState; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index fc0d36d13b2e..68202ef642c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -36,7 +36,7 @@ import androidx.annotation.BinderThread; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.Preconditions; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java index b757b00f16dd..ffda56d89276 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java @@ -28,7 +28,7 @@ import android.view.IWindowManager; import android.view.InputChannel; import android.view.InputEvent; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java index 42b8e9f5a3ad..c54e4cd90f57 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java @@ -59,7 +59,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.LinearLayout; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.ShellExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 495cd0075494..cec246923e2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -35,7 +35,7 @@ import android.os.Bundle; import android.os.Debug; import android.view.SurfaceControl; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.animation.FloatProperties; import com.android.wm.shell.common.FloatingContentCoordinator; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 9c1e321a1273..4f62192eaf5b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -33,7 +33,7 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUtils; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 56a465a4889a..a9f3b54006d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -51,7 +51,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java index d093f1e5ccc1..bb8d4ee9c80f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java @@ -23,7 +23,7 @@ import android.view.VelocityTracker; import android.view.ViewConfiguration; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index ad298dcc253e..814eaae1ea74 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -43,7 +43,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; 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 c67cf1d85918..e46625debcc6 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 @@ -63,7 +63,7 @@ import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt index 7c5f10a5bcca..8ee72b499e5a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt @@ -76,21 +76,40 @@ class TaskStackTransitionObserver( continue } + // Filter out changes that we care about if (change.mode == WindowManager.TRANSIT_OPEN) { change.taskInfo?.let { taskInfoList.add(it) } transitionTypeList.add(change.mode) } } - transitionToTransitionChanges.put( - transition, - TransitionChanges(taskInfoList, transitionTypeList) - ) + // Only add the transition to map if it has a change we care about + if (taskInfoList.isNotEmpty()) { + transitionToTransitionChanges.put( + transition, + TransitionChanges(taskInfoList, transitionTypeList) + ) + } } } override fun onTransitionStarting(transition: IBinder) {} - override fun onTransitionMerged(merged: IBinder, playing: IBinder) {} + override fun onTransitionMerged(merged: IBinder, playing: IBinder) { + val mergedTransitionChanges = + transitionToTransitionChanges.get(merged) + ?: + // We are adding changes of the merged transition to changes of the playing + // transition so if there is no changes nothing to do. + return + + transitionToTransitionChanges.remove(merged) + val playingTransitionChanges = transitionToTransitionChanges.get(playing) + if (playingTransitionChanges != null) { + playingTransitionChanges.merge(mergedTransitionChanges) + } else { + transitionToTransitionChanges.put(playing, mergedTransitionChanges) + } + } override fun onTransitionFinished(transition: IBinder, aborted: Boolean) { val taskInfoList = @@ -138,6 +157,11 @@ class TaskStackTransitionObserver( private data class TransitionChanges( val taskInfoList: MutableList<RunningTaskInfo> = ArrayList(), - val transitionTypeList: MutableList<Int> = ArrayList() - ) + val transitionTypeList: MutableList<Int> = ArrayList(), + ) { + fun merge(transitionChanges: TransitionChanges) { + taskInfoList.addAll(transitionChanges.taskInfoList) + transitionTypeList.addAll(transitionChanges.transitionTypeList) + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index 64e26dbd70be..1cbb8bbe5f75 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -23,7 +23,7 @@ import android.view.SurfaceSession; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index f5fbae55960a..27fd309c09b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -24,7 +24,7 @@ import android.view.SurfaceSession; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index dd219d32bbaa..b4941a5b49b5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -73,7 +73,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 0541a0287179..b3dab8527617 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -46,7 +46,7 @@ import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.split.SplitDecorManager; import com.android.wm.shell.protolog.ShellProtoLogGroup; 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 45eff4a24898..d9e97766e4fe 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 @@ -119,7 +119,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 0f3d6cade95a..1076eca60369 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -45,7 +45,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java index 1d8a8d506c5c..e0f63940663a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java @@ -36,7 +36,7 @@ import android.view.LayoutInflater; import android.view.WindowManager; import android.view.WindowManagerGlobal; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.split.SplitScreenConstants; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java index 2b12a22f907d..90eb22f369df 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java @@ -74,7 +74,7 @@ import com.android.internal.graphics.palette.Palette; import com.android.internal.graphics.palette.Quantizer; import com.android.internal.graphics.palette.VariationalKMeansQuantizer; import com.android.internal.policy.PhoneWindow; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.TransactionPool; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java index e552e6cdacf3..08211ab5df9c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java @@ -53,7 +53,7 @@ import android.window.SplashScreenView; import android.window.StartingWindowInfo; import android.window.StartingWindowRemovalInfo; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ContrastColorUtil; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java index 3353c7bd81c2..97a695f34cf7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java @@ -43,7 +43,7 @@ import android.window.StartingWindowRemovalInfo; import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index 8fc54edcbd4b..6e084d6e05a4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -48,7 +48,7 @@ import android.window.SnapshotDrawerUtils; import android.window.StartingWindowInfo; import android.window.TaskSnapshot; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.view.BaseIWindow; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java index 72fc8686f648..2036d9c13f0c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java @@ -35,7 +35,7 @@ import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS; import android.window.StartingWindowInfo; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java index 2e6ddc363906..aa9f15c37531 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java @@ -18,7 +18,7 @@ package com.android.wm.shell.sysui; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.io.PrintWriter; import java.util.Arrays; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java index 5ced1fb41a41..0202b6cf3eab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java @@ -40,7 +40,7 @@ import android.view.SurfaceControlRegistry; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; import com.android.wm.shell.common.ExternalInterfaceBinder; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java index 2e2f569a52b8..3a680097554f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java @@ -25,7 +25,7 @@ import android.view.SurfaceControl; import androidx.annotation.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import java.util.ArrayList; 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 8ee1efa90a30..4f4b8097cfac 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 @@ -39,7 +39,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.common.split.SplitScreenUtils; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java index c33fb80fdefc..c8921d256d7f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -28,7 +28,7 @@ import android.os.IBinder; import android.view.SurfaceControl; import android.window.TransitionInfo; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 9412b2b0b243..73b32a24246a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -53,6 +53,7 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; +import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE; import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN; @@ -102,7 +103,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.TransitionAnimation; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.common.DisplayController; @@ -944,12 +945,15 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } private static int getWallpaperTransitType(TransitionInfo info) { + boolean hasWallpaper = false; boolean hasOpenWallpaper = false; boolean hasCloseWallpaper = false; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) { + if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0 + || (change.getFlags() & FLAG_IS_WALLPAPER) != 0) { + hasWallpaper = true; if (TransitionUtil.isOpeningType(change.getMode())) { hasOpenWallpaper = true; } else if (TransitionUtil.isClosingType(change.getMode())) { @@ -965,6 +969,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return WALLPAPER_TRANSITION_OPEN; } else if (hasCloseWallpaper) { return WALLPAPER_TRANSITION_CLOSE; + } else if (hasWallpaper) { + return WALLPAPER_TRANSITION_CHANGE; } else { return WALLPAPER_TRANSITION_NONE; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java index 89b0e25b306b..978b8da2eb6d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java @@ -28,7 +28,7 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.window.IWindowContainerTransactionCallback; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; /** * Utilities and interfaces for transition-like usage on top of the legacy app-transition and diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java index e8b01b5880fb..8cc7f212af25 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java @@ -35,7 +35,7 @@ import android.annotation.Nullable; import android.view.SurfaceControl; import android.window.TransitionInfo; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -184,7 +184,8 @@ public class MixedTransitionHelper { for (int i = info.getChanges().size() - 1; i >= 0; --i) { TransitionInfo.Change change = info.getChanges().get(i); - if (change == pipChange || !isOpeningMode(change.getMode())) { + if (change == pipChange || !isOpeningMode(change.getMode()) || + change.getTaskInfo() == null) { // Ignore the change/task that's going into Pip or not opening continue; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java index 69c41675e989..c5dc668582bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -30,7 +30,7 @@ import android.window.TransitionRequestInfo; import android.window.WindowAnimationState; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java index 9fc6702562bb..391c5fe3473c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -30,7 +30,7 @@ import android.view.SurfaceControl; import android.window.TransitionInfo; import android.window.WindowContainerTransaction; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index d6860464d055..6013a1ea1d9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -39,7 +39,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.BinderThread; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.TransitionUtil; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index 2047b5a88604..a5f071af6973 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -54,7 +54,7 @@ import android.window.TransitionInfo; import com.android.internal.R; import com.android.internal.policy.TransitionAnimation; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.TransitionUtil; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index f6e38dac859c..ec6802da85f6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -75,7 +75,7 @@ import androidx.annotation.BinderThread; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index 7c2ba455c0c9..88bfebf9331e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -33,7 +33,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt index 564e716c7378..c93e584aff9f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/KtProtoLog.kt @@ -18,7 +18,7 @@ package com.android.wm.shell.util import android.util.Log import com.android.internal.protolog.common.IProtoLogGroup -import com.android.internal.protolog.common.ProtoLog +import com.android.internal.protolog.ProtoLog /** * Log messages using an API similar to [com.android.internal.protolog.common.ProtoLog]. Useful for diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS new file mode 100644 index 000000000000..482aaab6bc74 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/OWNERS @@ -0,0 +1 @@ +per-file KtProtolog.kt = file:platform/development:/tools/winscope/OWNERS diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index e1009a0ae8bb..a05dbf844db0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -26,7 +26,6 @@ import static android.view.InputDevice.SOURCE_TOUCHSCREEN; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_HOVER_ENTER; import static android.view.MotionEvent.ACTION_HOVER_EXIT; -import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static android.view.WindowInsets.Type.statusBars; @@ -74,7 +73,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.Cuj; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; @@ -103,6 +102,7 @@ import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; +import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; import java.io.PrintWriter; import java.util.Objects; @@ -383,10 +383,32 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mWindowDecorByTaskId.remove(taskInfo.taskId); } + private void onMaximizeOrRestore(int taskId, String tag) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); + if (decoration == null) { + return; + } + InteractionJankMonitorUtils.beginTracing( + Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, mContext, decoration.mTaskSurface, tag); + mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo); + decoration.closeHandleMenu(); + decoration.closeMaximizeMenu(); + } + + private void onSnapResize(int taskId, boolean left) { + final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId); + if (decoration == null) { + return; + } + mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo, + left ? SnapPosition.LEFT : SnapPosition.RIGHT); + decoration.closeHandleMenu(); + decoration.closeMaximizeMenu(); + } + private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener, View.OnGenericMotionListener, DragDetector.MotionEventHandler { - private static final int CLOSE_MAXIMIZE_MENU_DELAY_MS = 150; private final int mTaskId; private final WindowContainerToken mTaskToken; @@ -405,7 +427,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private boolean mTouchscreenInUse; private boolean mHasLongClicked; private int mDragPointerId = -1; - private final Runnable mCloseMaximizeWindowRunnable; private DesktopModeTouchEventListener( RunningTaskInfo taskInfo, @@ -416,11 +437,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragDetector = new DragDetector(this); mGestureDetector = new GestureDetector(mContext, this); mDisplayId = taskInfo.displayId; - mCloseMaximizeWindowRunnable = () -> { - final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); - if (decoration == null) return; - decoration.closeMaximizeMenu(); - }; } @Override @@ -472,31 +488,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (id == R.id.collapse_menu_button) { decoration.closeHandleMenu(); } else if (id == R.id.maximize_window) { - InteractionJankMonitorUtils.beginTracing( - Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, /* view= */ v, - /* tag= */ "caption_bar_button"); - final RunningTaskInfo taskInfo = decoration.mTaskInfo; - decoration.closeHandleMenu(); - decoration.closeMaximizeMenu(); - mDesktopTasksController.toggleDesktopTaskSize(taskInfo); - } else if (id == R.id.maximize_menu_maximize_button) { - InteractionJankMonitorUtils.beginTracing( - Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, /* view= */ v, - /* tag= */ "maximize_menu_option"); - final RunningTaskInfo taskInfo = decoration.mTaskInfo; - mDesktopTasksController.toggleDesktopTaskSize(taskInfo); - decoration.closeHandleMenu(); - decoration.closeMaximizeMenu(); - } else if (id == R.id.maximize_menu_snap_left_button) { - final RunningTaskInfo taskInfo = decoration.mTaskInfo; - mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.LEFT); - decoration.closeHandleMenu(); - decoration.closeMaximizeMenu(); - } else if (id == R.id.maximize_menu_snap_right_button) { - final RunningTaskInfo taskInfo = decoration.mTaskInfo; - mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.RIGHT); - decoration.closeHandleMenu(); - decoration.closeMaximizeMenu(); + // TODO(b/346441962): move click detection logic into the decor's + // {@link AppHeaderViewHolder}. Let it encapsulate the that and have it report + // back to the decoration using + // {@link DesktopModeWindowDecoration#setOnMaximizeOrRestoreClickListener}, which + // should shared with the maximize menu's maximize/restore actions. + onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button"); } } @@ -578,40 +575,26 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return false; } + /** + * TODO(b/346441962): move this hover detection logic into the decor's + * {@link AppHeaderViewHolder}. + */ @Override public boolean onGenericMotion(View v, MotionEvent ev) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); final int id = v.getId(); - if (ev.getAction() == ACTION_HOVER_ENTER) { - if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) { - decoration.onMaximizeWindowHoverEnter(); - } else if (id == R.id.maximize_window - || MaximizeMenu.Companion.isMaximizeMenuView(id)) { - // Re-hovering over any of the maximize menu views should keep the menu open by - // cancelling any attempts to close the menu. - mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable); - if (id != R.id.maximize_window) { - decoration.onMaximizeMenuHoverEnter(id, ev); - } + if (ev.getAction() == ACTION_HOVER_ENTER && id == R.id.maximize_window) { + decoration.setAppHeaderMaximizeButtonHovered(true); + if (!decoration.isMaximizeMenuActive()) { + decoration.onMaximizeButtonHoverEnter(); } return true; - } else if (ev.getAction() == ACTION_HOVER_MOVE - && MaximizeMenu.Companion.isMaximizeMenuView(id)) { - decoration.onMaximizeMenuHoverMove(id, ev); - mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable); - } else if (ev.getAction() == ACTION_HOVER_EXIT) { - if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) { - decoration.onMaximizeWindowHoverExit(); - } else if (id == R.id.maximize_window - || MaximizeMenu.Companion.isMaximizeMenuView(id)) { - // Close menu if not hovering over maximize menu or maximize button after a - // delay to give user a chance to re-enter view or to move from one maximize - // menu view to another. - mMainHandler.postDelayed(mCloseMaximizeWindowRunnable, - CLOSE_MAXIMIZE_MENU_DELAY_MS); - if (id != R.id.maximize_window) { - decoration.onMaximizeMenuHoverExit(id, ev); - } + } + if (ev.getAction() == ACTION_HOVER_EXIT && id == R.id.maximize_window) { + decoration.setAppHeaderMaximizeButtonHovered(false); + decoration.onMaximizeHoverStateChanged(); + if (!decoration.isMaximizeMenuActive()) { + decoration.onMaximizeButtonHoverExit(); } return true; } @@ -719,11 +702,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && action != MotionEvent.ACTION_CANCEL)) { return false; } - final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); - InteractionJankMonitorUtils.beginTracing( - Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, mContext, - /* surface= */ decoration.mTaskSurface, /* tag= */ "double_tap"); - mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo); + onMaximizeOrRestore(mTaskId, "double_tap"); return true; } } @@ -1105,7 +1084,13 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { final DesktopModeTouchEventListener touchEventListener = new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback); - + windowDecoration.setOnMaximizeOrRestoreClickListener(this::onMaximizeOrRestore); + windowDecoration.setOnLeftSnapClickListener((taskId, tag) -> { + onSnapResize(taskId, true /* isLeft */); + }); + windowDecoration.setOnRightSnapClickListener((taskId, tag) -> { + onSnapResize(taskId, false /* isLeft */); + }); windowDecoration.setCaptionListeners( touchEventListener, touchEventListener, touchEventListener, touchEventListener); windowDecoration.setExclusionRegionListener(mExclusionRegionListener); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 4d597cac889e..f53c21d352b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -69,6 +69,7 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; @@ -87,6 +88,9 @@ import java.util.function.Supplier; public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> { private static final String TAG = "DesktopModeWindowDecoration"; + @VisibleForTesting + static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L; + private final Handler mHandler; private final Choreographer mChoreographer; private final SyncTransactionQueue mSyncQueue; @@ -96,6 +100,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private View.OnTouchListener mOnCaptionTouchListener; private View.OnLongClickListener mOnCaptionLongClickListener; private View.OnGenericMotionListener mOnCaptionGenericMotionListener; + private OnTaskActionClickListener mOnMaximizeOrRestoreClickListener; + private OnTaskActionClickListener mOnLeftSnapClickListener; + private OnTaskActionClickListener mOnRightSnapClickListener; private DragPositioningCallback mDragPositioningCallback; private DragResizeInputListener mDragResizeListener; private DragDetector mDragDetector; @@ -120,6 +127,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private ExclusionRegionListener mExclusionRegionListener; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private final MaximizeMenuFactory mMaximizeMenuFactory; + + // Hover state for the maximize menu and button. The menu will remain open as long as either of + // these is true. See {@link #onMaximizeHoverStateChanged()}. + private boolean mIsAppHeaderMaximizeButtonHovered = false; + private boolean mIsMaximizeMenuHovered = false; + // Used to schedule the closing of the maximize menu when neither of the button or menu are + // being hovered. There's a small delay after stopping the hover, to allow a quick reentry + // to cancel the close. + private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu; DesktopModeWindowDecoration( Context context, @@ -135,7 +152,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, SurfaceControl::new, - new SurfaceControlViewHostFactory() {}); + new SurfaceControlViewHostFactory() {}, + DefaultMaximizeMenuFactory.INSTANCE); } DesktopModeWindowDecoration( @@ -152,7 +170,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, - SurfaceControlViewHostFactory surfaceControlViewHostFactory) { + SurfaceControlViewHostFactory surfaceControlViewHostFactory, + MaximizeMenuFactory maximizeMenuFactory) { super(context, displayController, taskOrganizer, taskInfo, taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, windowContainerTransactionSupplier, surfaceControlSupplier, @@ -161,6 +180,31 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mChoreographer = choreographer; mSyncQueue = syncQueue; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; + mMaximizeMenuFactory = maximizeMenuFactory; + } + + /** + * Register a listener to be called back when one of the tasks' maximize/restore action is + * triggered. + * TODO(b/346441962): hook this up to double-tap and the header's maximize button, instead of + * having the ViewModel deal with parsing motion events. + */ + void setOnMaximizeOrRestoreClickListener(OnTaskActionClickListener listener) { + mOnMaximizeOrRestoreClickListener = listener; + } + + /** + * Register a listener to be called back when one of the tasks snap-left action is triggered. + */ + void setOnLeftSnapClickListener(OnTaskActionClickListener listener) { + mOnLeftSnapClickListener = listener; + } + + /** + * Register a listener to be called back when one of the tasks' snap-right action is triggered. + */ + void setOnRightSnapClickListener(OnTaskActionClickListener listener) { + mOnRightSnapClickListener = listener; } void setCaptionListeners( @@ -714,11 +758,41 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Create and display maximize menu window */ void createMaximizeMenu() { - mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer, - mDisplayController, mTaskInfo, mOnCaptionButtonClickListener, - mOnCaptionGenericMotionListener, mOnCaptionTouchListener, mContext, + mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer, + mDisplayController, mTaskInfo, mContext, calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier); - mMaximizeMenu.show(); + mMaximizeMenu.show( + mOnMaximizeOrRestoreClickListener, + mOnLeftSnapClickListener, + mOnRightSnapClickListener, + hovered -> { + mIsMaximizeMenuHovered = hovered; + onMaximizeHoverStateChanged(); + return null; + } + ); + } + + /** Set whether the app header's maximize button is hovered. */ + void setAppHeaderMaximizeButtonHovered(boolean hovered) { + mIsAppHeaderMaximizeButtonHovered = hovered; + onMaximizeHoverStateChanged(); + } + + /** + * Called when either one of the maximize button in the app header or the maximize menu has + * changed its hover state. + */ + void onMaximizeHoverStateChanged() { + if (!mIsMaximizeMenuHovered && !mIsAppHeaderMaximizeButtonHovered) { + // Neither is hovered, close the menu. + if (isMaximizeMenuActive()) { + mHandler.postDelayed(mCloseMaximizeWindowRunnable, CLOSE_MAXIMIZE_MENU_DELAY_MS); + } + return; + } + // At least one of the two is hovered, cancel the close if needed. + mHandler.removeCallbacks(mCloseMaximizeWindowRunnable); } /** @@ -992,34 +1066,22 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin .setAnimatingTaskResize(animatingTaskResize); } - /** Called when there is a {@Link ACTION_HOVER_EXIT} on the maximize window button. */ - void onMaximizeWindowHoverExit() { + /** + * Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button. + */ + void onMaximizeButtonHoverExit() { ((AppHeaderViewHolder) mWindowDecorViewHolder) .onMaximizeWindowHoverExit(); } - /** Called when there is a {@Link ACTION_HOVER_ENTER} on the maximize window button. */ - void onMaximizeWindowHoverEnter() { + /** + * Called when there is a {@link MotionEvent#ACTION_HOVER_ENTER} on the maximize window button. + */ + void onMaximizeButtonHoverEnter() { ((AppHeaderViewHolder) mWindowDecorViewHolder) .onMaximizeWindowHoverEnter(); } - /** Called when there is a {@Link ACTION_HOVER_ENTER} on a view in the maximize menu. */ - void onMaximizeMenuHoverEnter(int id, MotionEvent ev) { - mMaximizeMenu.onMaximizeMenuHoverEnter(id, ev); - } - - /** Called when there is a {@Link ACTION_HOVER_MOVE} on a view in the maximize menu. */ - void onMaximizeMenuHoverMove(int id, MotionEvent ev) { - mMaximizeMenu.onMaximizeMenuHoverMove(id, ev); - } - - /** Called when there is a {@Link ACTION_HOVER_EXIT} on a view in the maximize menu. */ - void onMaximizeMenuHoverExit(int id, MotionEvent ev) { - mMaximizeMenu.onMaximizeMenuHoverExit(id, ev); - } - - @Override public String toString() { return "{" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index d902444d4b15..a3616f65f3b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -55,7 +55,7 @@ import android.view.ViewConfiguration; import android.view.WindowManagerGlobal; import android.window.InputTransferToken; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt index 0470367015ea..5f9f8d6d1764 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt @@ -20,7 +20,6 @@ import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.annotation.ColorInt -import android.annotation.IdRes import android.app.ActivityManager.RunningTaskInfo import android.content.Context import android.content.res.ColorStateList @@ -28,6 +27,7 @@ import android.content.res.Resources import android.graphics.Paint import android.graphics.PixelFormat import android.graphics.PointF +import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.LayerDrawable @@ -37,16 +37,17 @@ import android.graphics.drawable.shapes.RoundRectShape import android.util.StateSet import android.view.LayoutInflater import android.view.MotionEvent +import android.view.MotionEvent.ACTION_HOVER_ENTER +import android.view.MotionEvent.ACTION_HOVER_EXIT +import android.view.MotionEvent.ACTION_HOVER_MOVE import android.view.SurfaceControl import android.view.SurfaceControl.Transaction import android.view.SurfaceControlViewHost import android.view.View -import android.view.View.OnClickListener -import android.view.View.OnGenericMotionListener -import android.view.View.OnTouchListener import android.view.View.SCALE_Y import android.view.View.TRANSLATION_Y import android.view.View.TRANSLATION_Z +import android.view.ViewGroup import android.view.WindowManager import android.view.WindowlessWindowManager import android.widget.Button @@ -64,10 +65,10 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHo import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.OPACITY_12 import com.android.wm.shell.windowdecor.common.OPACITY_40 +import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener import com.android.wm.shell.windowdecor.common.withAlpha import java.util.function.Supplier - /** * Menu that appears when user long clicks the maximize button. Gives the user the option to * maximize the task or snap the task to the right or left half of the screen. @@ -77,9 +78,6 @@ class MaximizeMenu( private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer, private val displayController: DisplayController, private val taskInfo: RunningTaskInfo, - private val onClickListener: OnClickListener, - private val onGenericMotionListener: OnGenericMotionListener, - private val onTouchListener: OnTouchListener, private val decorWindowContext: Context, private val menuPosition: PointF, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() } @@ -102,9 +100,19 @@ class MaximizeMenu( } /** Creates and shows the maximize window. */ - fun show() { + fun show( + onMaximizeClickListener: OnTaskActionClickListener, + onLeftSnapClickListener: OnTaskActionClickListener, + onRightSnapClickListener: OnTaskActionClickListener, + onHoverListener: (Boolean) -> Unit + ) { if (maximizeMenu != null) return - createMaximizeMenu() + createMaximizeMenu( + onMaximizeClickListener = onMaximizeClickListener, + onLeftSnapClickListener = onLeftSnapClickListener, + onRightSnapClickListener = onRightSnapClickListener, + onHoverListener = onHoverListener + ) maximizeMenuView?.animateOpenMenu() } @@ -117,7 +125,12 @@ class MaximizeMenu( } /** Create a maximize menu that is attached to the display area. */ - private fun createMaximizeMenu() { + private fun createMaximizeMenu( + onMaximizeClickListener: OnTaskActionClickListener, + onLeftSnapClickListener: OnTaskActionClickListener, + onRightSnapClickListener: OnTaskActionClickListener, + onHoverListener: (Boolean) -> Unit + ) { val t = transactionSupplier.get() val builder = SurfaceControl.Builder() rootTdaOrganizer.attachToDisplayArea(taskInfo.displayId, builder) @@ -146,11 +159,19 @@ class MaximizeMenu( context = decorWindowContext, menuHeight = menuHeight, menuPadding = menuPadding, - onClickListener = onClickListener, - onTouchListener = onTouchListener, - onGenericMotionListener = onGenericMotionListener, ).also { menuView -> + val taskId = taskInfo.taskId menuView.bind(taskInfo) + menuView.onMaximizeClickListener = { + onMaximizeClickListener.onClick(taskId, "maximize_menu_option") + } + menuView.onLeftSnapClickListener = { + onLeftSnapClickListener.onClick(taskId, "left_snap_option") + } + menuView.onRightSnapClickListener = { + onRightSnapClickListener.onClick(taskId, "right_snap_option") + } + menuView.onMenuHoverListener = onHoverListener viewHost.setView(menuView.rootView, lp) } @@ -198,56 +219,6 @@ class MaximizeMenu( } /** - * Called when a [MotionEvent.ACTION_HOVER_ENTER] is triggered on any of the menu's views. - * - * TODO(b/346440693): this is only needed for the left/right snap options that don't support - * selector states to manage its hover state. Look into whether that can be added to avoid - * manually tracking hover enter/exit motion events. Also because those button colors/states - * aren't updating correctly for pressed, focused and selected states. - * See also [onMaximizeMenuHoverMove] and [onMaximizeMenuHoverExit]. - */ - fun onMaximizeMenuHoverEnter(viewId: Int, ev: MotionEvent) { - setSnapButtonsColorOnHover(viewId, ev) - } - - /** Called when a [MotionEvent.ACTION_HOVER_MOVE] is triggered on any of the menu's views. */ - fun onMaximizeMenuHoverMove(viewId: Int, ev: MotionEvent) { - setSnapButtonsColorOnHover(viewId, ev) - } - - /** Called when a [MotionEvent.ACTION_HOVER_EXIT] is triggered on any of the menu's views. */ - fun onMaximizeMenuHoverExit(id: Int, ev: MotionEvent) { - val snapOptionsWidth = maximizeMenuView?.snapOptionsWidth ?: return - val snapOptionsHeight = maximizeMenuView?.snapOptionsHeight ?: return - val inSnapMenuBounds = ev.x >= 0 && ev.x <= snapOptionsWidth && - ev.y >= 0 && ev.y <= snapOptionsHeight - - if (id == R.id.maximize_menu_snap_menu_layout && !inSnapMenuBounds) { - // After exiting the snap menu layout area, checks to see that user is not still - // hovering within the snap menu layout bounds which would indicate that the user is - // hovering over a snap button within the snap menu layout rather than having exited. - maximizeMenuView?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.NONE) - } - } - - private fun setSnapButtonsColorOnHover(viewId: Int, ev: MotionEvent) { - val snapOptionsWidth = maximizeMenuView?.snapOptionsWidth ?: return - val snapMenuCenter = snapOptionsWidth / 2 - when { - viewId == R.id.maximize_menu_snap_left_button || - (viewId == R.id.maximize_menu_snap_menu_layout && ev.x <= snapMenuCenter) -> { - maximizeMenuView - ?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.LEFT) - } - viewId == R.id.maximize_menu_snap_right_button || - (viewId == R.id.maximize_menu_snap_menu_layout && ev.x > snapMenuCenter) -> { - maximizeMenuView - ?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.RIGHT) - } - } - } - - /** * The view within the Maximize Menu, presents maximize, restore and snap-to-side options for * resizing a Task. */ @@ -255,12 +226,11 @@ class MaximizeMenu( context: Context, private val menuHeight: Int, private val menuPadding: Int, - onClickListener: OnClickListener, - onTouchListener: OnTouchListener, - onGenericMotionListener: OnGenericMotionListener, ) { - val rootView: View = LayoutInflater.from(context) - .inflate(R.layout.desktop_mode_window_decor_maximize_menu, null /* root */) + val rootView = LayoutInflater.from(context) + .inflate(R.layout.desktop_mode_window_decor_maximize_menu, null /* root */) as ViewGroup + private val container = requireViewById(R.id.container) + private val overlay = requireViewById(R.id.maximize_menu_overlay) private val maximizeText = requireViewById(R.id.maximize_menu_maximize_window_text) as TextView private val maximizeButton = @@ -285,30 +255,63 @@ class MaximizeMenu( private val fillRadius = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_fill_radius) + private val hoverTempRect = Rect() private val openMenuAnimatorSet = AnimatorSet() private lateinit var taskInfo: RunningTaskInfo private lateinit var style: MenuStyle - /** The width of the snap menu option view, including both left and right snaps. */ - val snapOptionsWidth: Int - get() = snapButtonsLayout.width - /** The height of the snap menu option view, including both left and right snaps .*/ - val snapOptionsHeight: Int - get() = snapButtonsLayout.height + /** Invoked when the maximize or restore option is clicked. */ + var onMaximizeClickListener: (() -> Unit)? = null + /** Invoked when the left snap option is clicked. */ + var onLeftSnapClickListener: (() -> Unit)? = null + /** Invoked when the right snap option is clicked. */ + var onRightSnapClickListener: (() -> Unit)? = null + /** Invoked whenever the hover state of the menu changes. */ + var onMenuHoverListener: ((Boolean) -> Unit)? = null init { - // TODO(b/346441962): encapsulate menu hover enter/exit logic inside this class and - // expose only what is actually relevant to outside classes so that specific checks - // against resource IDs aren't needed outside this class. - rootView.setOnGenericMotionListener(onGenericMotionListener) - rootView.setOnTouchListener(onTouchListener) - maximizeButton.setOnClickListener(onClickListener) - maximizeButton.setOnGenericMotionListener(onGenericMotionListener) - snapRightButton.setOnClickListener(onClickListener) - snapRightButton.setOnGenericMotionListener(onGenericMotionListener) - snapLeftButton.setOnClickListener(onClickListener) - snapLeftButton.setOnGenericMotionListener(onGenericMotionListener) - snapButtonsLayout.setOnGenericMotionListener(onGenericMotionListener) + overlay.setOnHoverListener { _, event -> + // The overlay covers the entire menu, so it's a convenient way to monitor whether + // the menu is hovered as a whole or not. + when (event.action) { + ACTION_HOVER_ENTER -> onMenuHoverListener?.invoke(true) + ACTION_HOVER_EXIT -> onMenuHoverListener?.invoke(false) + } + + // Also check if the hover falls within the snap options layout, to manually + // set the left/right state based on the event's position. + // TODO(b/346440693): this manual hover tracking is needed for left/right snap + // because its view/background(s) don't support selector states. Look into whether + // that can be added to avoid manual tracking. Also because these button + // colors/state logic is only being applied on hover events, but there's pressed, + // focused and selected states that should be responsive too. + val snapLayoutBoundsRelToOverlay = hoverTempRect.also { rect -> + snapButtonsLayout.getDrawingRect(rect) + rootView.offsetDescendantRectToMyCoords(snapButtonsLayout, rect) + } + if (event.action == ACTION_HOVER_ENTER || event.action == ACTION_HOVER_MOVE) { + if (snapLayoutBoundsRelToOverlay.contains(event.x.toInt(), event.y.toInt())) { + // Hover is inside the snap layout, anything left of center is the left + // snap, and anything right of center is right snap. + val layoutCenter = snapLayoutBoundsRelToOverlay.centerX() + if (event.x < layoutCenter) { + updateSplitSnapSelection(SnapToHalfSelection.LEFT) + } else { + updateSplitSnapSelection(SnapToHalfSelection.RIGHT) + } + } else { + // Any other hover is outside the snap layout, so neither is selected. + updateSplitSnapSelection(SnapToHalfSelection.NONE) + } + } + + // Don't consume the event to allow child views to receive the event too. + return@setOnHoverListener false + } + + maximizeButton.setOnClickListener { onMaximizeClickListener?.invoke() } + snapRightButton.setOnClickListener { onRightSnapClickListener?.invoke() } + snapLeftButton.setOnClickListener { onLeftSnapClickListener?.invoke() } // To prevent aliasing. maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null) @@ -351,7 +354,7 @@ class MaximizeMenu( val value = animatedValue as Float val topPadding = menuPadding - ((1 - value) * menuHeight).toInt() - rootView.setPadding(menuPadding, topPadding, + container.setPadding(menuPadding, topPadding, menuPadding, menuPadding) } }, @@ -410,7 +413,7 @@ class MaximizeMenu( } /** Update the view state to a new snap to half selection. */ - fun updateSplitSnapSelection(selection: SnapToHalfSelection) { + private fun updateSplitSnapSelection(selection: SnapToHalfSelection) { when (selection) { SnapToHalfSelection.NONE -> deactivateSnapOptions() SnapToHalfSelection.LEFT -> activateSnapOption(activateLeft = true) @@ -638,13 +641,41 @@ class MaximizeMenu( private const val ELEVATION_ANIMATION_DURATION_MS = 50L private const val CONTROLS_ALPHA_ANIMATION_DELAY_MS = 33L private const val MENU_Z_TRANSLATION = 1f - fun isMaximizeMenuView(@IdRes viewId: Int): Boolean { - return viewId == R.id.maximize_menu || - viewId == R.id.maximize_menu_maximize_button || - viewId == R.id.maximize_menu_snap_left_button || - viewId == R.id.maximize_menu_snap_right_button || - viewId == R.id.maximize_menu_snap_menu_layout || - viewId == R.id.maximize_menu_snap_menu_layout - } + } +} + +/** A factory interface to create a [MaximizeMenu]. */ +interface MaximizeMenuFactory { + fun create( + syncQueue: SyncTransactionQueue, + rootTdaOrganizer: RootTaskDisplayAreaOrganizer, + displayController: DisplayController, + taskInfo: RunningTaskInfo, + decorWindowContext: Context, + menuPosition: PointF, + transactionSupplier: Supplier<Transaction> + ): MaximizeMenu +} + +/** A [MaximizeMenuFactory] implementation that creates a [MaximizeMenu]. */ +object DefaultMaximizeMenuFactory : MaximizeMenuFactory { + override fun create( + syncQueue: SyncTransactionQueue, + rootTdaOrganizer: RootTaskDisplayAreaOrganizer, + displayController: DisplayController, + taskInfo: RunningTaskInfo, + decorWindowContext: Context, + menuPosition: PointF, + transactionSupplier: Supplier<Transaction> + ): MaximizeMenu { + return MaximizeMenu( + syncQueue, + rootTdaOrganizer, + displayController, + taskInfo, + decorWindowContext, + menuPosition, + transactionSupplier + ) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt new file mode 100644 index 000000000000..14b9e7f71622 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.windowdecor.common + +/** A callback to be invoked when a Task's window decor element is clicked. */ +fun interface OnTaskActionClickListener { + /** + * Called when a task's decor element has been clicked. + * + * @param taskId the id of the task. + * @param tag a readable identifier for the element. + */ + fun onClick(taskId: Int, tag: String) +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index f9b4108bc8c2..8303317d39fc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -687,6 +687,25 @@ public class ShellTaskOrganizerTests extends ShellTestCase { verify(mRecentTasksController).onTaskRunningInfoChanged(task2); } + @Test + public void testTaskVanishedCallback() { + RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN); + mOrganizer.onTaskAppeared(task1, /* leash= */ null); + + RunningTaskInfo[] vanishedTasks = new RunningTaskInfo[1]; + ShellTaskOrganizer.TaskVanishedListener listener = + new ShellTaskOrganizer.TaskVanishedListener() { + @Override + public void onTaskVanished(RunningTaskInfo taskInfo) { + vanishedTasks[0] = taskInfo; + } + }; + mOrganizer.addTaskVanishedListener(listener); + mOrganizer.onTaskVanished(task1); + + assertEquals(vanishedTasks[0], task1); + } + private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java index 51a20ee9d090..a2df22c5468f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java @@ -26,7 +26,7 @@ import android.testing.TestableContext; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import org.junit.After; import org.junit.Before; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index 731f75bf9f5d..55b6bd278f20 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -21,10 +21,12 @@ import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.calculateParentBounds; +import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.shouldUseJumpCutForAnimation; import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; @@ -40,6 +42,8 @@ import android.graphics.Rect; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; import android.window.TransitionInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -281,6 +285,18 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim actualParentBounds); } + @Test + public void testShouldUseJumpCutForAnimation() { + final Animation noopAnimation = new AlphaAnimation(0f, 1f); + assertTrue("Animation without duration should use jump cut.", + shouldUseJumpCutForAnimation(noopAnimation)); + + final Animation alphaAnimation = new AlphaAnimation(0f, 1f); + alphaAnimation.setDuration(100); + assertFalse("Animation with duration should not use jump cut.", + shouldUseJumpCutForAnimation(alphaAnimation)); + } + @NonNull private static TransitionInfo.Change prepareChangeForParentBoundsCalculationTest( @NonNull Point endRelOffset, @NonNull Rect endAbsBounds, @NonNull Point endParentSize) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 14fa0f1a338d..0e53e10cde08 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RunningTaskInfo +import android.app.KeyguardManager import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM @@ -149,6 +150,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var syncQueue: SyncTransactionQueue @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock lateinit var transitions: Transitions + @Mock lateinit var keyguardManager: KeyguardManager @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler @Mock @@ -233,6 +235,7 @@ class DesktopTasksControllerTest : ShellTestCase() { rootTaskDisplayAreaOrganizer, dragAndDropController, transitions, + keyguardManager, enterDesktopTransitionHandler, exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, @@ -1301,6 +1304,17 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun handleRequest_freeformTask_keyguardLocked_returnNull() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(keyguardManager.isKeyguardLocked).thenReturn(true) + val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY) + + val result = controller.handleRequest(Binder(), createTransition(freeformTask)) + + assertNull(result, "Should NOT handle request") + } + + @Test fun handleRequest_notOpenOrToFrontTransition_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java index a64ebd301c00..840126421c08 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java @@ -76,6 +76,8 @@ public class DragAndDropControllerTest extends ShellTestCase { @Mock private ShellCommandHandler mShellCommandHandler; @Mock + private ShellTaskOrganizer mShellTaskOrganizer; + @Mock private DisplayController mDisplayController; @Mock private UiEventLogger mUiEventLogger; @@ -96,8 +98,8 @@ public class DragAndDropControllerTest extends ShellTestCase { public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); mController = new DragAndDropController(mContext, mShellInit, mShellController, - mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider, - mGlobalDragListener, mTransitions, mMainExecutor); + mShellCommandHandler, mShellTaskOrganizer, mDisplayController, mUiEventLogger, + mIconProvider, mGlobalDragListener, mTransitions, mMainExecutor); mController.onInit(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index 6e72e8df8d62..582fb91559e5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -65,8 +65,6 @@ import android.content.res.Resources; import android.graphics.Insets; import android.os.RemoteException; import android.view.DisplayInfo; -import android.view.DragEvent; -import android.view.View; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -76,7 +74,6 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target; import com.android.wm.shell.splitscreen.SplitScreenController; -import com.android.wm.shell.startingsurface.TaskSnapshotWindow; import org.junit.After; import org.junit.Before; @@ -106,6 +103,8 @@ public class DragAndDropPolicyTest extends ShellTestCase { // Both the split-screen and start interface. @Mock private SplitScreenController mSplitScreenStarter; + @Mock + private DragAndDropPolicy.Starter mFullscreenStarter; @Mock private InstanceId mLoggerSessionId; @@ -151,7 +150,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false); mInsets = Insets.of(0, 0, 0, 0); - mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter)); + mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mFullscreenStarter)); mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY); mLaunchableIntentPendingIntent = mock(PendingIntent.class); when(mLaunchableIntentPendingIntent.getCreatorUserHandle()) @@ -285,13 +284,13 @@ public class DragAndDropPolicyTest extends ShellTestCase { setRunningTask(mHomeTask); DragSession dragSession = new DragSession(mActivityTaskManager, mLandscapeDisplayLayout, data, 0 /* dragFlags */); - dragSession.update(); + dragSession.initialize(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN)); - verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(), + verify(mFullscreenStarter).startIntent(any(), anyInt(), any(), eq(SPLIT_POSITION_UNDEFINED), any()); } @@ -300,7 +299,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, mLandscapeDisplayLayout, data, 0 /* dragFlags */); - dragSession.update(); + dragSession.initialize(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); @@ -320,7 +319,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, mPortraitDisplayLayout, data, 0 /* dragFlags */); - dragSession.update(); + dragSession.initialize(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); @@ -340,7 +339,7 @@ public class DragAndDropPolicyTest extends ShellTestCase { setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, mLandscapeDisplayLayout, mActivityClipData, 0 /* dragFlags */); - dragSession.update(); + dragSession.initialize(); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = mPolicy.getTargets(mInsets); for (Target t : targets) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index d38fc6cb6418..75d21457b60b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -34,7 +34,6 @@ import static org.mockito.Mockito.when; import static java.lang.Integer.MAX_VALUE; -import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; @@ -183,7 +182,7 @@ public class PipControllerTest extends ShellTestCase { @Test public void instantiatePipController_registersPipTransitionCallback() { - verify(mMockPipTransitionController).registerPipTransitionCallback(any()); + verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any()); } @Test @@ -235,27 +234,6 @@ public class PipControllerTest extends ShellTestCase { } @Test - public void onActivityHidden_isLastPipComponentName_clearLastPipComponent() { - final ComponentName component1 = new ComponentName(mContext, "component1"); - when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1); - - mPipController.mPinnedTaskListener.onActivityHidden(component1); - - verify(mMockPipBoundsState).setLastPipComponentName(null); - } - - @Test - public void onActivityHidden_isNotLastPipComponentName_lastPipComponentNotCleared() { - final ComponentName component1 = new ComponentName(mContext, "component1"); - final ComponentName component2 = new ComponentName(mContext, "component2"); - when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1); - - mPipController.mPinnedTaskListener.onActivityHidden(component2); - - verify(mMockPipBoundsState, never()).setLastPipComponentName(null); - } - - @Test public void saveReentryState_savesPipBoundsState() { final Rect bounds = new Rect(0, 0, 10, 10); when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index ace09a82d71c..66f8c0b9558d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -114,8 +114,8 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState, mSizeSpecSource); - final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState, - mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm, + final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor, + mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator, Optional.empty() /* pipPerfHintControllerOptional */); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java index 92762fa68550..6d18e3696f84 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java @@ -116,8 +116,8 @@ public class PipTouchHandlerTest extends ShellTestCase { mPipSnapAlgorithm = new PipSnapAlgorithm(); mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm, new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource); - PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState, - mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm, + PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor, + mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm, mMockPipTransitionController, mFloatingContentCoordinator, Optional.empty() /* pipPerfHintControllerOptional */); mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt index f9599702e763..0e5efa650cc4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt @@ -48,7 +48,6 @@ import org.mockito.kotlin.same import org.mockito.kotlin.verify import org.mockito.kotlin.whenever - /** * Test class for {@link TaskStackTransitionObserver} * @@ -168,6 +167,80 @@ class TaskStackTransitionObserverTest { .isEqualTo(freeformOpenChange.taskInfo?.windowingMode) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL) + fun transitionMerged_withChange_onlyOpenChangeIsNotified() { + val listener = TestListener() + val executor = TestShellExecutor() + transitionObserver.addTaskStackTransitionObserverListener(listener, executor) + + // Create open transition + val change = + createChange( + WindowManager.TRANSIT_OPEN, + createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM) + ) + val transitionInfo = + TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build() + + // create change transition to be merged to above transition + val mergedChange = + createChange( + WindowManager.TRANSIT_CHANGE, + createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM) + ) + val mergedTransitionInfo = + TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0).addChange(mergedChange).build() + val mergedTransition = Mockito.mock(IBinder::class.java) + + callOnTransitionReady(transitionInfo) + callOnTransitionReady(mergedTransitionInfo, mergedTransition) + callOnTransitionMerged(mergedTransition) + callOnTransitionFinished() + executor.flushAll() + + assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId) + assertThat(listener.taskInfoToBeNotified.windowingMode) + .isEqualTo(change.taskInfo?.windowingMode) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL) + fun transitionMerged_withOpen_lastOpenChangeIsNotified() { + val listener = TestListener() + val executor = TestShellExecutor() + transitionObserver.addTaskStackTransitionObserverListener(listener, executor) + + // Create open transition + val change = + createChange( + WindowManager.TRANSIT_OPEN, + createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM) + ) + val transitionInfo = + TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build() + + // create change transition to be merged to above transition + val mergedChange = + createChange( + WindowManager.TRANSIT_OPEN, + createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM) + ) + val mergedTransitionInfo = + TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(mergedChange).build() + val mergedTransition = Mockito.mock(IBinder::class.java) + + callOnTransitionReady(transitionInfo) + callOnTransitionReady(mergedTransitionInfo, mergedTransition) + callOnTransitionMerged(mergedTransition) + callOnTransitionFinished() + executor.flushAll() + + assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(mergedChange.taskInfo?.taskId) + assertThat(listener.taskInfoToBeNotified.windowingMode) + .isEqualTo(mergedChange.taskInfo?.windowingMode) + } + class TestListener : TaskStackTransitionObserver.TaskStackTransitionObserverListener { var taskInfoToBeNotified = ActivityManager.RunningTaskInfo() @@ -179,11 +252,14 @@ class TaskStackTransitionObserverTest { } /** Simulate calling the onTransitionReady() method */ - private fun callOnTransitionReady(transitionInfo: TransitionInfo) { + private fun callOnTransitionReady( + transitionInfo: TransitionInfo, + transition: IBinder = mockTransitionBinder + ) { val startT = Mockito.mock(SurfaceControl.Transaction::class.java) val finishT = Mockito.mock(SurfaceControl.Transaction::class.java) - transitionObserver.onTransitionReady(mockTransitionBinder, transitionInfo, startT, finishT) + transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT) } /** Simulate calling the onTransitionFinished() method */ @@ -191,6 +267,11 @@ class TaskStackTransitionObserverTest { transitionObserver.onTransitionFinished(mockTransitionBinder, false) } + /** Simulate calling the onTransitionMerged() method */ + private fun callOnTransitionMerged(merged: IBinder, playing: IBinder = mockTransitionBinder) { + transitionObserver.onTransitionMerged(merged, playing) + } + companion object { fun createTaskInfo(taskId: Int, windowingMode: Int): ActivityManager.RunningTaskInfo { val taskInfo = ActivityManager.RunningTaskInfo() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java new file mode 100644 index 000000000000..b54c3bf72110 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; + +import static org.mockito.Mockito.mock; + +import android.app.ActivityManager.RunningTaskInfo; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.WindowManager; +import android.window.TransitionInfo; + +public class ChangeBuilder { + final TransitionInfo.Change mChange; + + ChangeBuilder(@WindowManager.TransitionType int mode) { + mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true)); + mChange.setMode(mode); + } + + ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) { + mChange.setFlags(flags); + return this; + } + + ChangeBuilder setTask(RunningTaskInfo taskInfo) { + mChange.setTaskInfo(taskInfo); + return this; + } + + ChangeBuilder setRotate(int anim) { + return setRotate(Surface.ROTATION_90, anim); + } + + ChangeBuilder setRotate() { + return setRotate(ROTATION_ANIMATION_UNSPECIFIED); + } + + ChangeBuilder setRotate(@Surface.Rotation int target, int anim) { + mChange.setRotation(Surface.ROTATION_0, target); + mChange.setRotationAnimation(anim); + return this; + } + + TransitionInfo.Change build() { + return mChange; + } + + private static SurfaceControl createMockSurface(boolean valid) { + SurfaceControl sc = mock(SurfaceControl.class); + doReturn(valid).when(sc).isValid(); + return sc; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java new file mode 100644 index 000000000000..754a173ff069 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_SLEEP; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.TransitionInfo.FLAG_SYNC; +import static android.window.TransitionInfo.FLAG_TRANSLUCENT; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.sysui.ShellInit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for the default animation handler that is used if no other special-purpose handler picks + * up an animation request. + * + * Build/Install/Run: + * atest WMShellUnitTests:DefaultTransitionHandlerTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DefaultTransitionHandlerTest extends ShellTestCase { + + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + + private final DisplayController mDisplayController = mock(DisplayController.class); + private final TransactionPool mTransactionPool = new MockTransactionPool(); + private final TestShellExecutor mMainExecutor = new TestShellExecutor(); + private final TestShellExecutor mAnimExecutor = new TestShellExecutor(); + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + + private ShellInit mShellInit; + private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private DefaultTransitionHandler mTransitionHandler; + + @Before + public void setUp() { + mShellInit = new ShellInit(mMainExecutor); + mRootTaskDisplayAreaOrganizer = new RootTaskDisplayAreaOrganizer( + mMainExecutor, + mContext, + mShellInit); + mTransitionHandler = new DefaultTransitionHandler( + mContext, mShellInit, mDisplayController, + mTransactionPool, mMainExecutor, mMainHandler, mAnimExecutor, + mRootTaskDisplayAreaOrganizer); + mShellInit.init(); + } + + @After + public void tearDown() { + flushHandlers(); + } + + private void flushHandlers() { + mMainHandler.runWithScissors(() -> { + mAnimExecutor.flushAll(); + mMainExecutor.flushAll(); + }, 1000L); + } + + @Test + public void testAnimationBackgroundCreatedForTaskTransition() { + final TransitionInfo.Change openTask = new ChangeBuilder(TRANSIT_OPEN) + .setTask(createTaskInfo(1)) + .build(); + final TransitionInfo.Change closeTask = new ChangeBuilder(TRANSIT_TO_BACK) + .setTask(createTaskInfo(2)) + .build(); + + final IBinder token = new Binder(); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(openTask) + .addChange(closeTask) + .build(); + final SurfaceControl.Transaction startT = MockTransactionPool.create(); + final SurfaceControl.Transaction finishT = MockTransactionPool.create(); + + mTransitionHandler.startAnimation(token, info, startT, finishT, + mock(Transitions.TransitionFinishCallback.class)); + + mergeSync(mTransitionHandler, token); + flushHandlers(); + + verify(startT).setColor(any(), any()); + } + + @Test + public void testNoAnimationBackgroundForTranslucentTasks() { + final TransitionInfo.Change openTask = new ChangeBuilder(TRANSIT_OPEN) + .setTask(createTaskInfo(1)) + .setFlags(FLAG_TRANSLUCENT) + .build(); + final TransitionInfo.Change closeTask = new ChangeBuilder(TRANSIT_TO_BACK) + .setTask(createTaskInfo(2)) + .setFlags(FLAG_TRANSLUCENT) + .build(); + + final IBinder token = new Binder(); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(openTask) + .addChange(closeTask) + .build(); + final SurfaceControl.Transaction startT = MockTransactionPool.create(); + final SurfaceControl.Transaction finishT = MockTransactionPool.create(); + + mTransitionHandler.startAnimation(token, info, startT, finishT, + mock(Transitions.TransitionFinishCallback.class)); + + mergeSync(mTransitionHandler, token); + flushHandlers(); + + verify(startT, never()).setColor(any(), any()); + } + + @Test + public void testNoAnimationBackgroundForWallpapers() { + final TransitionInfo.Change openWallpaper = new ChangeBuilder(TRANSIT_OPEN) + .setFlags(TransitionInfo.FLAG_IS_WALLPAPER) + .build(); + final TransitionInfo.Change closeWallpaper = new ChangeBuilder(TRANSIT_TO_BACK) + .setFlags(TransitionInfo.FLAG_IS_WALLPAPER) + .build(); + + final IBinder token = new Binder(); + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(openWallpaper) + .addChange(closeWallpaper) + .build(); + final SurfaceControl.Transaction startT = MockTransactionPool.create(); + final SurfaceControl.Transaction finishT = MockTransactionPool.create(); + + mTransitionHandler.startAnimation(token, info, startT, finishT, + mock(Transitions.TransitionFinishCallback.class)); + + mergeSync(mTransitionHandler, token); + flushHandlers(); + + verify(startT, never()).setColor(any(), any()); + } + + private static void mergeSync(Transitions.TransitionHandler handler, IBinder token) { + handler.mergeAnimation( + new Binder(), + new TransitionInfoBuilder(TRANSIT_SLEEP, FLAG_SYNC).build(), + MockTransactionPool.create(), + token, + mock(Transitions.TransitionFinishCallback.class)); + } + + private static RunningTaskInfo createTaskInfo(int taskId) { + RunningTaskInfo taskInfo = new RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.topActivityType = ACTIVITY_TYPE_STANDARD; + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + taskInfo.configuration.windowConfiguration.setActivityType(taskInfo.topActivityType); + taskInfo.token = mock(WindowContainerToken.class); + return taskInfo; + } +} + diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java new file mode 100644 index 000000000000..574a87ac4b17 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import static org.mockito.Mockito.RETURNS_SELF; +import static org.mockito.Mockito.mock; + +import android.view.SurfaceControl; + +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.util.StubTransaction; + +public class MockTransactionPool extends TransactionPool { + + public static SurfaceControl.Transaction create() { + return mock(StubTransaction.class, RETURNS_SELF); + } + + @Override + public SurfaceControl.Transaction acquire() { + return create(); + } + + @Override + public void release(SurfaceControl.Transaction t) { + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 69a61eadf61d..8331d591fd59 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -79,7 +79,6 @@ import android.util.Pair; import android.view.IRecentsAnimationRunner; import android.view.Surface; import android.view.SurfaceControl; -import android.view.WindowManager; import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; import android.window.IWindowContainerToken; @@ -1615,43 +1614,6 @@ public class ShellTransitionTests extends ShellTestCase { eq(R.styleable.WindowAnimation_activityCloseEnterAnimation), anyBoolean()); } - class ChangeBuilder { - final TransitionInfo.Change mChange; - - ChangeBuilder(@WindowManager.TransitionType int mode) { - mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true)); - mChange.setMode(mode); - } - - ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) { - mChange.setFlags(flags); - return this; - } - - ChangeBuilder setTask(RunningTaskInfo taskInfo) { - mChange.setTaskInfo(taskInfo); - return this; - } - - ChangeBuilder setRotate(int anim) { - return setRotate(Surface.ROTATION_90, anim); - } - - ChangeBuilder setRotate() { - return setRotate(ROTATION_ANIMATION_UNSPECIFIED); - } - - ChangeBuilder setRotate(@Surface.Rotation int target, int anim) { - mChange.setRotation(Surface.ROTATION_0, target); - mChange.setRotationAnimation(anim); - return this; - } - - TransitionInfo.Change build() { - return mChange; - } - } - class TestTransitionHandler implements Transitions.TransitionHandler { ArrayList<Pair<IBinder, Transitions.TransitionFinishCallback>> mFinishes = new ArrayList<>(); @@ -1740,12 +1702,6 @@ public class ShellTransitionTests extends ShellTestCase { .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); } - private static SurfaceControl createMockSurface(boolean valid) { - SurfaceControl sc = mock(SurfaceControl.class); - doReturn(valid).when(sc).isValid(); - return sc; - } - private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, int activityType) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index ca1e3f173e24..4c94c2933383 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -67,6 +67,7 @@ import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopTasksController +import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.freeform.FreeformTaskTransitionStarter import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.sysui.KeyguardChangeListener @@ -75,6 +76,7 @@ import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener +import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener import java.util.Optional import java.util.function.Supplier import org.junit.Assert.assertEquals @@ -82,6 +84,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.anyInt @@ -518,6 +521,99 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } } + @Test + fun testOnDecorMaximizedOrRestored_togglesTaskSize() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture()) + return@let captor.value + } + + maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(mockDesktopTasksController).toggleDesktopTaskSize(decor.mTaskInfo) + } + + @Test + fun testOnDecorMaximizedOrRestored_closesMenus() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture()) + return@let captor.value + } + + maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(decor).closeHandleMenu() + verify(decor).closeMaximizeMenu() + } + + @Test + fun testOnDecorSnappedLeft_snapResizes() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnLeftSnapClickListener(captor.capture()) + return@let captor.value + } + + snapLeftListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.LEFT) + } + + @Test + fun testOnDecorSnappedLeft_closeMenus() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnLeftSnapClickListener(captor.capture()) + return@let captor.value + } + + snapLeftListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(decor).closeHandleMenu() + verify(decor).closeMaximizeMenu() + } + + @Test + fun testOnDecorSnappedRight_snapResizes() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnRightSnapClickListener(captor.capture()) + return@let captor.value + } + + snapLeftListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.RIGHT) + } + + @Test + fun testOnDecorSnappedRight_closeMenus() { + val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM)) + onTaskOpening(decor.mTaskInfo) + val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java) + .let { captor -> + verify(decor).setOnRightSnapClickListener(captor.capture()) + return@let captor.value + } + + snapLeftListener.onClick(decor.mTaskInfo.taskId, "test") + + verify(decor).closeHandleMenu() + verify(decor).closeMaximizeMenu() + } + private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) { desktopModeWindowDecorViewModel.onTaskOpening( task, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 46c158908226..36e8a4671a46 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -24,9 +24,14 @@ import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction; +import static com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.CLOSE_MAXIMIZE_MENU_DELAY_MS; 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.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; @@ -38,11 +43,13 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.ComponentName; +import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.PointF; import android.os.Handler; import android.os.SystemProperties; import android.platform.test.annotations.DisableFlags; @@ -62,6 +69,7 @@ import android.view.View; import android.view.WindowManager; import android.window.WindowContainerTransaction; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -76,6 +84,10 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams; +import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener; + +import kotlin.Unit; +import kotlin.jvm.functions.Function1; import org.junit.After; import org.junit.Before; @@ -84,6 +96,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.quality.Strictness; @@ -112,8 +125,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; @Mock - private Handler mMockHandler; - @Mock private Choreographer mMockChoreographer; @Mock private SyncTransactionQueue mMockSyncQueue; @@ -131,13 +142,18 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory; @Mock private TypedArray mMockRoundedCornersRadiusArray; - @Mock private TestTouchEventListener mMockTouchEventListener; @Mock private DesktopModeWindowDecoration.ExclusionRegionListener mMockExclusionRegionListener; @Mock private PackageManager mMockPackageManager; + @Mock + private Handler mMockHandler; + @Captor + private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener; + @Captor + private ArgumentCaptor<Runnable> mCloseMaxMenuRunnable; private final InsetsState mInsetsState = new InsetsState(); private SurfaceControl.Transaction mMockTransaction; @@ -459,6 +475,92 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { verify(mMockHandler).removeCallbacks(runnableArgument.getValue()); } + @Test + public void createMaximizeMenu_showsMenu() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final MaximizeMenu menu = mock(MaximizeMenu.class); + final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, + new FakeMaximizeMenuFactory(menu)); + assertFalse(decoration.isMaximizeMenuActive()); + + createMaximizeMenu(decoration, menu); + + assertTrue(decoration.isMaximizeMenuActive()); + } + + @Test + public void maximizeMenu_unHoversMenu_schedulesCloseMenu() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final MaximizeMenu menu = mock(MaximizeMenu.class); + final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, + new FakeMaximizeMenuFactory(menu)); + decoration.setAppHeaderMaximizeButtonHovered(false); + createMaximizeMenu(decoration, menu); + + mOnMaxMenuHoverChangeListener.getValue().invoke(false); + + verify(mMockHandler) + .postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS)); + + mCloseMaxMenuRunnable.getValue().run(); + verify(menu).close(); + assertFalse(decoration.isMaximizeMenuActive()); + } + + @Test + public void maximizeMenu_unHoversButton_schedulesCloseMenu() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final MaximizeMenu menu = mock(MaximizeMenu.class); + final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, + new FakeMaximizeMenuFactory(menu)); + decoration.setAppHeaderMaximizeButtonHovered(true); + createMaximizeMenu(decoration, menu); + + decoration.setAppHeaderMaximizeButtonHovered(false); + + verify(mMockHandler) + .postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS)); + + mCloseMaxMenuRunnable.getValue().run(); + verify(menu).close(); + assertFalse(decoration.isMaximizeMenuActive()); + } + + @Test + public void maximizeMenu_hoversMenu_cancelsCloseMenu() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final MaximizeMenu menu = mock(MaximizeMenu.class); + final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, + new FakeMaximizeMenuFactory(menu)); + createMaximizeMenu(decoration, menu); + + mOnMaxMenuHoverChangeListener.getValue().invoke(true); + + verify(mMockHandler).removeCallbacks(any()); + } + + @Test + public void maximizeMenu_hoversButton_cancelsCloseMenu() { + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */); + final MaximizeMenu menu = mock(MaximizeMenu.class); + final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo, + new FakeMaximizeMenuFactory(menu)); + createMaximizeMenu(decoration, menu); + + decoration.setAppHeaderMaximizeButtonHovered(true); + + verify(mMockHandler).removeCallbacks(any()); + } + + private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) { + final OnTaskActionClickListener l = (taskId, tag) -> {}; + decoration.setOnMaximizeOrRestoreClickListener(l); + decoration.setOnLeftSnapClickListener(l); + decoration.setOnRightSnapClickListener(l); + decoration.createMaximizeMenu(); + verify(menu).show(any(), any(), any(), mOnMaxMenuHoverChangeListener.capture()); + } + private void fillRoundedCornersResources(int fillValue) { when(mMockRoundedCornersRadiusArray.getDimensionPixelSize(anyInt(), anyInt())) .thenReturn(fillValue); @@ -479,12 +581,19 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private DesktopModeWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo) { - DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext, + return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory()); + } + + private DesktopModeWindowDecoration createWindowDecoration( + ActivityManager.RunningTaskInfo taskInfo, + MaximizeMenuFactory maximizeMenuFactory) { + final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer, SurfaceControl.Builder::new, mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, - mMockSurfaceControlViewHostFactory); + mMockSurfaceControlViewHostFactory, + maximizeMenuFactory); windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener); windowDecor.setExclusionRegionListener(mMockExclusionRegionListener); @@ -541,4 +650,27 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { return false; } } + + private static final class FakeMaximizeMenuFactory implements MaximizeMenuFactory { + private final MaximizeMenu mMaximizeMenu; + + FakeMaximizeMenuFactory() { + this(mock(MaximizeMenu.class)); + } + + FakeMaximizeMenuFactory(MaximizeMenu menu) { + mMaximizeMenu = menu; + } + + @NonNull + @Override + public MaximizeMenu create(@NonNull SyncTransactionQueue syncQueue, + @NonNull RootTaskDisplayAreaOrganizer rootTdaOrganizer, + @NonNull DisplayController displayController, + @NonNull ActivityManager.RunningTaskInfo taskInfo, + @NonNull Context decorWindowContext, @NonNull PointF menuPosition, + @NonNull Supplier<SurfaceControl.Transaction> transactionSupplier) { + return mMaximizeMenu; + } + } } diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java index c2632458435a..fd71f8694503 100644 --- a/media/java/android/media/AudioManagerInternal.java +++ b/media/java/android/media/AudioManagerInternal.java @@ -44,8 +44,9 @@ public abstract class AudioManagerInternal { * Add the UID for a new assistant service * * @param uid UID of the newly available assistants + * @param owningUid UID of the actual assistant app, if {@code uid} is a isolated proc */ - public abstract void addAssistantServiceUid(int uid); + public abstract void addAssistantServiceUid(int uid, int owningUid); /** * Remove the UID for an existing assistant service diff --git a/media/jni/Android.bp b/media/jni/Android.bp index e619e1c7425f..7f487e51f7e8 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -79,7 +79,6 @@ cc_library_shared { "libcamera_client", "libmtp", "libpiex", - "libprocessgroup", "libandroidfw", "libhidlallocatorutils", "libhidlbase", diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp index 1325fc161b77..028c97ec51f7 100644 --- a/media/tests/MediaFrameworkTest/Android.bp +++ b/media/tests/MediaFrameworkTest/Android.bp @@ -24,6 +24,7 @@ android_test { "flag-junit", "testng", "truth", + "collector-device-lib-platform", ], jni_libs: [ "libdexmakerjvmtiagent", diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java index 00068bda60dd..102d21acfd19 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java @@ -16,6 +16,8 @@ package com.android.mediaframeworktest.helpers; +import android.content.AttributionSourceState; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.ImageFormat; @@ -2227,4 +2229,24 @@ public class CameraTestUtils extends Assert { else return new Size(width, height); } + + /** + * Constructs an AttributionSourceState with only the uid, pid, and deviceId fields set + * + * <p>This method is a temporary stopgap in the transition to using AttributionSource. Currently + * AttributionSourceState is only used as a vehicle for passing deviceId, uid, and pid + * arguments.</p> + */ + public static AttributionSourceState getClientAttribution(Context context) { + // TODO: Send the full contextAttribution over aidl, remove USE_CALLING_* + AttributionSourceState contextAttribution = + context.getAttributionSource().asState(); + AttributionSourceState clientAttribution = + new AttributionSourceState(); + clientAttribution.uid = -1; // USE_CALLING_UID + clientAttribution.pid = -1; // USE_CALLING_PID + clientAttribution.deviceId = contextAttribution.deviceId; + clientAttribution.next = new AttributionSourceState[0]; + return clientAttribution; + } } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java index 353366d6c850..ad3374a7da6a 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java @@ -19,6 +19,7 @@ package com.android.mediaframeworktest.integration; import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; import static android.content.Context.DEVICE_ID_DEFAULT; +import android.content.AttributionSourceState; import android.hardware.CameraInfo; import android.hardware.ICamera; import android.hardware.ICameraClient; @@ -38,6 +39,8 @@ import android.util.Log; import androidx.test.filters.SmallTest; +import com.android.mediaframeworktest.helpers.CameraTestUtils; + /** * <p> * Junit / Instrumentation test case for the camera2 api @@ -78,8 +81,10 @@ public class CameraBinderTest extends AndroidTestCase { @SmallTest public void testNumberOfCameras() throws Exception { + AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext); + clientAttribution.deviceId = DEVICE_ID_DEFAULT; int numCameras = mUtils.getCameraService().getNumberOfCameras(CAMERA_TYPE_ALL, - DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT); + clientAttribution, DEVICE_POLICY_DEFAULT); assertTrue("At least this many cameras: " + mUtils.getGuessedNumCameras(), numCameras >= mUtils.getGuessedNumCameras()); Log.v(TAG, "Number of cameras " + numCameras); @@ -87,9 +92,11 @@ public class CameraBinderTest extends AndroidTestCase { @SmallTest public void testCameraInfo() throws Exception { + AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext); + clientAttribution.deviceId = DEVICE_ID_DEFAULT; for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId, - ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT, + ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution, DEVICE_POLICY_DEFAULT); assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1); assertTrue("Orientation was not set for camera " + cameraId, @@ -154,6 +161,10 @@ public class CameraBinderTest extends AndroidTestCase { @SmallTest public void testConnect() throws Exception { + AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext); + clientAttribution.deviceId = DEVICE_ID_DEFAULT; + clientAttribution.uid = ICameraService.USE_CALLING_UID; + clientAttribution.pid = ICameraService.USE_CALLING_PID; for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) { ICameraClient dummyCallbacks = new DummyCameraClient(); @@ -162,12 +173,10 @@ public class CameraBinderTest extends AndroidTestCase { ICamera cameraUser = mUtils.getCameraService() .connect(dummyCallbacks, cameraId, clientPackageName, - ICameraService.USE_CALLING_UID, - ICameraService.USE_CALLING_PID, getContext().getApplicationInfo().targetSdkVersion, ICameraService.ROTATION_OVERRIDE_NONE, /*forceSlowJpegMode*/false, - DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT); + clientAttribution, DEVICE_POLICY_DEFAULT); assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); Log.v(TAG, String.format("Camera %s connected", cameraId)); @@ -260,14 +269,18 @@ public class CameraBinderTest extends AndroidTestCase { String clientPackageName = getContext().getPackageName(); String clientAttributionTag = getContext().getAttributionTag(); + AttributionSourceState clientAttribution = + CameraTestUtils.getClientAttribution(mContext); + clientAttribution.deviceId = DEVICE_ID_DEFAULT; + clientAttribution.uid = ICameraService.USE_CALLING_UID; ICameraDeviceUser cameraUser = mUtils.getCameraService().connectDevice( dummyCallbacks, String.valueOf(cameraId), clientPackageName, clientAttributionTag, - ICameraService.USE_CALLING_UID, 0 /*oomScoreOffset*/, + 0 /*oomScoreOffset*/, getContext().getApplicationInfo().targetSdkVersion, - ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT, + ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution, DEVICE_POLICY_DEFAULT); assertNotNull(String.format("Camera %s was null", cameraId), cameraUser); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java index 6cf2a41573cd..0ab1ee9095e0 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; +import android.content.AttributionSourceState; import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.ICameraService; @@ -54,6 +55,7 @@ import android.view.Surface; import androidx.test.filters.SmallTest; import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner; +import com.android.mediaframeworktest.helpers.CameraTestUtils; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; @@ -245,10 +247,14 @@ public class CameraDeviceBinderTest extends AndroidTestCase { mMockCb = spy(dummyCallbacks); + AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext); + clientAttribution.deviceId = DEVICE_ID_DEFAULT; + clientAttribution.uid = ICameraService.USE_CALLING_UID; + mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId, - clientPackageName, clientAttributionTag, ICameraService.USE_CALLING_UID, + clientPackageName, clientAttributionTag, /*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion, - ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT); + ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution, DEVICE_POLICY_DEFAULT); assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser); mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); @@ -414,10 +420,13 @@ public class CameraDeviceBinderTest extends AndroidTestCase { @SmallTest public void testCameraCharacteristics() throws RemoteException { + AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext); + clientAttribution.deviceId = DEVICE_ID_DEFAULT; + CameraMetadataNative info = mUtils.getCameraService().getCameraCharacteristics(mCameraId, getContext().getApplicationInfo().targetSdkVersion, ICameraService.ROTATION_OVERRIDE_NONE, - DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT); + clientAttribution, DEVICE_POLICY_DEFAULT); assertFalse(info.isEmpty()); assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)); diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index 7e5bef1125f2..e91c7a9ecda8 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -244,6 +244,12 @@ int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNano ALOGE("%s: targetDurationNanos must be positive", __FUNCTION__); return EINVAL; } + { + std::scoped_lock lock(sHintMutex); + if (mTargetDurationNanos == targetDurationNanos) { + return 0; + } + } ndk::ScopedAStatus ret = mHintSession->updateTargetWorkDuration(targetDurationNanos); if (!ret.isOk()) { ALOGE("%s: HintSession updateTargetWorkDuration failed: %s", __FUNCTION__, diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index 78a53578f5ca..d19fa98f1171 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -159,6 +159,10 @@ TEST_F(PerformanceHintTest, TestSession) { int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos); EXPECT_EQ(0, result); + // subsequent call with same target should be ignored but return no error + result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos); + EXPECT_EQ(0, result); + usleep(2); // Sleep for longer than preferredUpdateRateNanos. int64_t actualDurationNanos = 20; std::vector<int64_t> actualDurations; diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig index b242a76ffae4..95945d77730d 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -110,3 +110,11 @@ flag { bug: "321311407" } +flag { + name: "nfc_persist_log" + is_exported: true + namespace: "nfc" + description: "Enable NFC persistent log support" + bug: "321310044" +} + diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 4ac3e671a378..8666584e0972 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -64,13 +64,6 @@ flag { } flag { - name: "allow_all_widgets_on_lockscreen_by_default" - namespace: "systemui" - description: "Allow all widgets on the lock screen by default." - bug: "328261690" -} - -flag { name: "enable_determining_advanced_details_header_with_metadata" namespace: "pixel_cross_device_control" description: "Use metadata instead of device type to determine whether a bluetooth device should use advanced details header." diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS index 7669e79b42be..f8c3a93cd3c7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS @@ -1,9 +1,4 @@ # Default reviewers for this and subdirectories. -siyuanh@google.com -hughchen@google.com -timhypeng@google.com -robertluo@google.com -songferngwang@google.com yqian@google.com chelseahao@google.com yiyishen@google.com diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java index c5e86b4fb280..4f2329bb75c2 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java @@ -327,4 +327,12 @@ public class PowerAllowlistBackend { return sInstance; } } + + /** Testing only. Reset the instance to avoid tests affecting each other. */ + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + public static void resetInstance() { + synchronized (PowerAllowlistBackend.class) { + sInstance = null; + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java index df0e61833269..8868837e4cff 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java @@ -23,19 +23,8 @@ import android.telephony.UiccPortInfo; import android.telephony.UiccSlotInfo; import android.telephony.UiccSlotMapping; -import java.util.List; - public class DataServiceUtils { - public static <T> boolean shouldUpdateEntityList(List<T> oldList, List<T> newList) { - if ((oldList != null && - (newList.isEmpty() || !newList.equals(oldList))) - || (!newList.isEmpty() && oldList == null)) { - return true; - } - return false; - } - /** * Represents columns of the MobileNetworkInfoData table, define these columns from * {@see MobileNetworkUtils} or relevant common APIs. @@ -52,73 +41,16 @@ public class DataServiceUtils { public static final String COLUMN_ID = "subId"; /** - * The name of the contact discovery enabled state column, - * {@see MobileNetworkUtils#isContactDiscoveryEnabled(Context, int)}. - */ - public static final String COLUMN_IS_CONTACT_DISCOVERY_ENABLED = - "isContactDiscoveryEnabled"; - - /** - * The name of the contact discovery visible state column, - * {@see MobileNetworkUtils#isContactDiscoveryEnabled(Context, int)}. - */ - public static final String COLUMN_IS_CONTACT_DISCOVERY_VISIBLE = - "isContactDiscoveryVisible"; - - /** * The name of the mobile network data state column, * {@see MobileNetworkUtils#isMobileDataEnabled(Context)}. */ public static final String COLUMN_IS_MOBILE_DATA_ENABLED = "isMobileDataEnabled"; /** - * The name of the CDMA option state column, - * {@see MobileNetworkUtils#isCdmaOptions(Context, int)}. - */ - public static final String COLUMN_IS_CDMA_OPTIONS = "isCdmaOptions"; - - /** - * The name of the GSM option state column, - * {@see MobileNetworkUtils#isGsmOptions(Context, int)}. - */ - public static final String COLUMN_IS_GSM_OPTIONS = "isGsmOptions"; - - /** - * The name of the world mode state column, - * {@see MobileNetworkUtils#isWorldMode(Context, int)}. - */ - public static final String COLUMN_IS_WORLD_MODE = "isWorldMode"; - - /** - * The name of the display network select options state column, - * {@see MobileNetworkUtils#shouldDisplayNetworkSelectOptions(Context, int)}. - */ - public static final String COLUMN_SHOULD_DISPLAY_NETWORK_SELECT_OPTIONS = - "shouldDisplayNetworkSelectOptions"; - - /** - * The name of the TDSCDMA supported state column, - * {@see MobileNetworkUtils#isTdscdmaSupported(Context, int)}. - */ - public static final String COLUMN_IS_TDSCDMA_SUPPORTED = "isTdscdmaSupported"; - - /** - * The name of the active network is cellular state column, - * {@see MobileNetworkUtils#activeNetworkIsCellular(Context)}. - */ - public static final String COLUMN_ACTIVE_NETWORK_IS_CELLULAR = "activeNetworkIsCellular"; - - /** * The name of the show toggle for physicalSim state column, * {@see SubscriptionUtil#showToggleForPhysicalSim(SubscriptionManager)}. */ public static final String COLUMN_SHOW_TOGGLE_FOR_PHYSICAL_SIM = "showToggleForPhysicalSim"; - - /** - * The name of the subscription's data roaming state column, - * {@see TelephonyManager#isDataRoamingEnabled()}. - */ - public static final String COLUMN_IS_DATA_ROAMING_ENABLED = "isDataRoamingEnabled"; } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java index e72346d6c754..13f99e9387de 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java @@ -26,23 +26,11 @@ import androidx.room.PrimaryKey; @Entity(tableName = DataServiceUtils.MobileNetworkInfoData.TABLE_NAME) public class MobileNetworkInfoEntity { - public MobileNetworkInfoEntity(@NonNull String subId, boolean isContactDiscoveryEnabled, - boolean isContactDiscoveryVisible, boolean isMobileDataEnabled, boolean isCdmaOptions, - boolean isGsmOptions, boolean isWorldMode, boolean shouldDisplayNetworkSelectOptions, - boolean isTdscdmaSupported, boolean activeNetworkIsCellular, - boolean showToggleForPhysicalSim, boolean isDataRoamingEnabled) { + public MobileNetworkInfoEntity(@NonNull String subId, boolean isMobileDataEnabled, + boolean showToggleForPhysicalSim) { this.subId = subId; - this.isContactDiscoveryEnabled = isContactDiscoveryEnabled; - this.isContactDiscoveryVisible = isContactDiscoveryVisible; this.isMobileDataEnabled = isMobileDataEnabled; - this.isCdmaOptions = isCdmaOptions; - this.isGsmOptions = isGsmOptions; - this.isWorldMode = isWorldMode; - this.shouldDisplayNetworkSelectOptions = shouldDisplayNetworkSelectOptions; - this.isTdscdmaSupported = isTdscdmaSupported; - this.activeNetworkIsCellular = activeNetworkIsCellular; this.showToggleForPhysicalSim = showToggleForPhysicalSim; - this.isDataRoamingEnabled = isDataRoamingEnabled; } @PrimaryKey @@ -50,55 +38,18 @@ public class MobileNetworkInfoEntity { @NonNull public String subId; - @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_CONTACT_DISCOVERY_ENABLED) - public boolean isContactDiscoveryEnabled; - - @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_CONTACT_DISCOVERY_VISIBLE) - public boolean isContactDiscoveryVisible; - @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_MOBILE_DATA_ENABLED) public boolean isMobileDataEnabled; - @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_CDMA_OPTIONS) - public boolean isCdmaOptions; - - @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_GSM_OPTIONS) - public boolean isGsmOptions; - - @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_WORLD_MODE) - public boolean isWorldMode; - - @ColumnInfo(name = - DataServiceUtils.MobileNetworkInfoData.COLUMN_SHOULD_DISPLAY_NETWORK_SELECT_OPTIONS) - public boolean shouldDisplayNetworkSelectOptions; - - @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_TDSCDMA_SUPPORTED) - public boolean isTdscdmaSupported; - - @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_ACTIVE_NETWORK_IS_CELLULAR) - public boolean activeNetworkIsCellular; - @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_SHOW_TOGGLE_FOR_PHYSICAL_SIM) public boolean showToggleForPhysicalSim; - @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_DATA_ROAMING_ENABLED) - public boolean isDataRoamingEnabled; - @Override public int hashCode() { int result = 17; result = 31 * result + subId.hashCode(); - result = 31 * result + Boolean.hashCode(isContactDiscoveryEnabled); - result = 31 * result + Boolean.hashCode(isContactDiscoveryVisible); result = 31 * result + Boolean.hashCode(isMobileDataEnabled); - result = 31 * result + Boolean.hashCode(isCdmaOptions); - result = 31 * result + Boolean.hashCode(isGsmOptions); - result = 31 * result + Boolean.hashCode(isWorldMode); - result = 31 * result + Boolean.hashCode(shouldDisplayNetworkSelectOptions); - result = 31 * result + Boolean.hashCode(isTdscdmaSupported); - result = 31 * result + Boolean.hashCode(activeNetworkIsCellular); result = 31 * result + Boolean.hashCode(showToggleForPhysicalSim); - result = 31 * result + Boolean.hashCode(isDataRoamingEnabled); return result; } @@ -113,45 +64,18 @@ public class MobileNetworkInfoEntity { MobileNetworkInfoEntity info = (MobileNetworkInfoEntity) obj; return TextUtils.equals(subId, info.subId) - && isContactDiscoveryEnabled == info.isContactDiscoveryEnabled - && isContactDiscoveryVisible == info.isContactDiscoveryVisible && isMobileDataEnabled == info.isMobileDataEnabled - && isCdmaOptions == info.isCdmaOptions - && isGsmOptions == info.isGsmOptions - && isWorldMode == info.isWorldMode - && shouldDisplayNetworkSelectOptions == info.shouldDisplayNetworkSelectOptions - && isTdscdmaSupported == info.isTdscdmaSupported - && activeNetworkIsCellular == info.activeNetworkIsCellular - && showToggleForPhysicalSim == info.showToggleForPhysicalSim - && isDataRoamingEnabled == info.isDataRoamingEnabled; + && showToggleForPhysicalSim == info.showToggleForPhysicalSim; } public String toString() { StringBuilder builder = new StringBuilder(); builder.append(" {MobileNetworkInfoEntity(subId = ") .append(subId) - .append(", isContactDiscoveryEnabled = ") - .append(isContactDiscoveryEnabled) - .append(", isContactDiscoveryVisible = ") - .append(isContactDiscoveryVisible) .append(", isMobileDataEnabled = ") .append(isMobileDataEnabled) - .append(", isCdmaOptions = ") - .append(isCdmaOptions) - .append(", isGsmOptions = ") - .append(isGsmOptions) - .append(", isWorldMode = ") - .append(isWorldMode) - .append(", shouldDisplayNetworkSelectOptions = ") - .append(shouldDisplayNetworkSelectOptions) - .append(", isTdscdmaSupported = ") - .append(isTdscdmaSupported) .append(", activeNetworkIsCellular = ") - .append(activeNetworkIsCellular) - .append(", showToggleForPhysicalSim = ") .append(showToggleForPhysicalSim) - .append(", isDataRoamingEnabled = ") - .append(isDataRoamingEnabled) .append(")}"); return builder.toString(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt index 8ec5ba193dba..837c682ca98a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt @@ -46,6 +46,7 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -98,6 +99,7 @@ class AudioRepositoryImpl( private val contentResolver: ContentResolver, private val backgroundCoroutineContext: CoroutineContext, private val coroutineScope: CoroutineScope, + private val logger: Logger, ) : AudioRepository { private val streamSettingNames: Map<AudioStream, String> = @@ -170,6 +172,7 @@ class AudioRepositoryImpl( .conflate() .map { getCurrentAudioStream(audioStream) } .onStart { emit(getCurrentAudioStream(audioStream)) } + .onEach { logger.onVolumeUpdateReceived(audioStream, it) } .flowOn(backgroundCoroutineContext) } @@ -193,6 +196,7 @@ class AudioRepositoryImpl( override suspend fun setVolume(audioStream: AudioStream, volume: Int) { withContext(backgroundCoroutineContext) { + logger.onSetVolumeRequested(audioStream, volume) audioManager.setStreamVolume(audioStream.value, volume, 0) } } @@ -247,4 +251,11 @@ class AudioRepositoryImpl( awaitClose { contentResolver.unregisterContentObserver(observer) } } } + + interface Logger { + + fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) + + fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel) + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt index 9c48299d81be..c8e4d715117f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt @@ -17,6 +17,7 @@ package com.android.settingslib.volume.shared.model import android.media.AudioManager +import android.media.AudioSystem /** Type-safe wrapper for [AudioManager] audio stream. */ @JvmInline @@ -25,6 +26,8 @@ value class AudioStream(val value: Int) { require(value in supportedStreamTypes) { "Unsupported stream=$value" } } + override fun toString(): String = AudioSystem.streamToString(value) + companion object { val supportedStreamTypes = setOf( diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt index 727c61ca1ae0..a7e04640d069 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt @@ -498,7 +498,7 @@ open class WifiUtils { ): Job = coroutineScope.launch { val wifiManager = context.getSystemService(WifiManager::class.java) ?: return@launch - if (wifiManager.queryWepAllowed()) { + if (wifiManager.isWepSupported == true && wifiManager.queryWepAllowed()) { onAllowed() } else { val intent = Intent(Intent.ACTION_MAIN).apply { diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt index 844dc12ee911..0e43acb04c91 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt @@ -64,6 +64,7 @@ class AudioRepositoryTest { @Mock private lateinit var communicationDevice: AudioDeviceInfo @Mock private lateinit var contentResolver: ContentResolver + private val logger = FakeAudioRepositoryLogger() private val eventsReceiver = FakeAudioManagerEventsReceiver() private val volumeByStream: MutableMap<Int, Int> = mutableMapOf() private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf() @@ -109,6 +110,7 @@ class AudioRepositoryTest { contentResolver, testScope.testScheduler, testScope.backgroundScope, + logger, ) } @@ -173,6 +175,15 @@ class AudioRepositoryTest { underTest.setVolume(audioStream, 50) runCurrent() + assertThat(logger.logs) + .isEqualTo( + listOf( + "onVolumeUpdateReceived audioStream=STREAM_SYSTEM", + "onSetVolumeRequested audioStream=STREAM_SYSTEM", + "onVolumeUpdateReceived audioStream=STREAM_SYSTEM", + "onVolumeUpdateReceived audioStream=STREAM_SYSTEM", + ) + ) assertThat(streamModel) .isEqualTo( AudioStreamModel( diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt new file mode 100644 index 000000000000..389bf5304262 --- /dev/null +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.volume.data.repository + +import com.android.settingslib.volume.shared.model.AudioStream +import com.android.settingslib.volume.shared.model.AudioStreamModel + +class FakeAudioRepositoryLogger : AudioRepositoryImpl.Logger { + + private val mutableLogs: MutableList<String> = mutableListOf() + val logs: List<String> + get() = mutableLogs + + override fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) { + synchronized(mutableLogs) { + mutableLogs.add("onSetVolumeRequested audioStream=$audioStream") + } + } + + override fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel) { + synchronized(mutableLogs) { + mutableLogs.add("onVolumeUpdateReceived audioStream=$audioStream") + } + } +} diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index d54236e60dd7..54f0d7a4d853 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1189,6 +1189,8 @@ public class SettingsProvider extends ContentProvider { synchronized (mLock) { if (getSyncDisabledModeConfigLocked() != SYNC_DISABLED_MODE_NONE) { + Slog.v(LOG_TAG, "did not write settings for prefix '" + + prefix + "' because sync is disabled"); return SET_ALL_RESULT_DISABLED; } final int key = makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 05eb0449495f..861c405b1542 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -203,6 +203,12 @@ final class SettingsState { private static final String NULL_VALUE = "null"; + // TOBO(b/312444587): remove after Test Mission 2. + // Bulk sync names + private static final String BULK_SYNC_MARKER = "aconfigd_marker/bulk_synced"; + private static final String BULK_SYNC_TRIGGER_COUNTER = + "core_experiments_team_internal/BulkSyncTriggerCounterFlag__bulk_sync_trigger_counter"; + private static final ArraySet<String> sSystemPackages = new ArraySet<>(); private final Object mWriteLock = new Object(); @@ -409,8 +415,7 @@ final class SettingsState { } } // TOBO(b/312444587): remove the comparison logic after Test Mission 2. - if (mSettings.get("aconfigd_marker/bulk_synced").value.equals("true") - && requests == null) { + if (requests == null) { Map<String, AconfigdFlagInfo> aconfigdFlagMap = AconfigdJavaUtils.listFlagsValueInNewStorage(localSocket); compareFlagValueInNewStorage( @@ -534,7 +539,7 @@ final class SettingsState { return null; } AconfigdFlagInfo flag = flagInfoDefault.get(fullFlagName); - if (flag == null) { + if (flag == null || !namespace.equals(flag.getNamespace())) { return null; } @@ -553,15 +558,33 @@ final class SettingsState { public ProtoOutputStream handleBulkSyncToNewStorage( Map<String, AconfigdFlagInfo> aconfigFlagMap) { // get marker or add marker if it does not exist - final String bulkSyncMarkerName = new String("aconfigd_marker/bulk_synced"); - Setting markerSetting = mSettings.get(bulkSyncMarkerName); + Setting markerSetting = mSettings.get(BULK_SYNC_MARKER); + int localCounter = 0; if (markerSetting == null) { - markerSetting = new Setting(bulkSyncMarkerName, "false", false, "aconfig", "aconfig"); - mSettings.put(bulkSyncMarkerName, markerSetting); + markerSetting = new Setting(BULK_SYNC_MARKER, "0", false, "aconfig", "aconfig"); + mSettings.put(BULK_SYNC_MARKER, markerSetting); + } + try { + localCounter = Integer.parseInt(markerSetting.value); + } catch(NumberFormatException e) { + // reset local counter + markerSetting.value = "0"; } if (enableAconfigStorageDaemon()) { - if (markerSetting.value.equals("true")) { + Setting bulkSyncCounter = mSettings.get(BULK_SYNC_TRIGGER_COUNTER); + int serverCounter = 0; + if (bulkSyncCounter != null) { + try { + serverCounter = Integer.parseInt(bulkSyncCounter.value); + } catch (NumberFormatException e) { + // reset the local value of server counter + bulkSyncCounter.value = "0"; + } + } + + boolean shouldSync = localCounter < serverCounter; + if (!shouldSync) { // CASE 1, flag is on, bulk sync marker true, nothing to do return null; } else { @@ -600,20 +623,12 @@ final class SettingsState { } // mark sync has been done - markerSetting.value = "true"; + markerSetting.value = String.valueOf(serverCounter); scheduleWriteIfNeededLocked(); return requests; } } else { - if (markerSetting.value.equals("true")) { - // CASE 3, flag is off, bulk sync marker true, clear the marker - markerSetting.value = "false"; - scheduleWriteIfNeededLocked(); - return null; - } else { - // CASE 4, flag is off, bulk sync marker false, nothing to do - return null; - } + return null; } } @@ -692,6 +707,7 @@ final class SettingsState { .setFlagName(flag.getName()) .setDefaultFlagValue(flagValue) .setIsReadWrite(isReadWrite) + .setNamespace(flag.getNamespace()) .build()); } } diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java index 94aeb9b8dcc4..4b4ced3c0753 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java @@ -151,12 +151,14 @@ public class SettingsStateTest { .setFlagName("flag1") .setDefaultFlagValue("false") .setIsReadWrite(true) + .setNamespace("test_namespace") .build(); AconfigdFlagInfo flag2 = AconfigdFlagInfo.newBuilder() .setPackageName("com.android.flags") .setFlagName("flag2") .setDefaultFlagValue("true") .setIsReadWrite(false) + .setNamespace("test_namespace") .build(); Map<String, AconfigdFlagInfo> flagInfoDefault = new HashMap<>(); @@ -1018,12 +1020,17 @@ public class SettingsStateTest { .setFlagName("flag1") .setDefaultFlagValue("false") .setIsReadWrite(true) + .setNamespace("test_namespace") .build(); flagInfoDefault.put(flag1.getFullFlagName(), flag1); - // server override + // not the right namespace + assertNull( + settingsState.getFlagOverrideToSync( + "some_namespace/com.android.flags.flag1", "true", flagInfoDefault)); + // server override settingsState.getFlagOverrideToSync( "test_namespace/com.android.flags.flag1", "true", flagInfoDefault); assertEquals("com.android.flags", flag1.getPackageName()); @@ -1079,21 +1086,45 @@ public class SettingsStateTest { .setIsReadWrite(false) .build()); + String bulkSyncMarker = "aconfigd_marker/bulk_synced"; + String bulkSyncCounter = + "core_experiments_team_internal/" + + "BulkSyncTriggerCounterFlag__bulk_sync_trigger_counter"; + synchronized (lock) { - settingsState.insertSettingLocked( - "aconfigd_marker/bulk_synced", "false", null, false, "aconfig"); + settingsState.insertSettingLocked(bulkSyncMarker, "0", null, false, "aconfig"); + settingsState.insertSettingLocked(bulkSyncCounter, "1", null, false, + "com.google.android.platform.core_experiments_team_internal"); // first bulk sync ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags); assertTrue(requests != null); String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); - assertEquals("true", value); + assertEquals("1", value); // send time should no longer bulk sync requests = settingsState.handleBulkSyncToNewStorage(flags); - assertTrue(requests == null); + assertNull(requests); + value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); + assertEquals("1", value); + + // won't sync if the marker is string + settingsState.insertSettingLocked(bulkSyncMarker, "true", null, false, "aconfig"); + settingsState.insertSettingLocked(bulkSyncCounter, "0", null, false, + "com.google.android.platform.core_experiments_team_internal"); + requests = settingsState.handleBulkSyncToNewStorage(flags); + assertNull(requests); value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); - assertEquals("true", value); + assertEquals("0", value); + + // won't sync if the marker and counter value are the same + settingsState.insertSettingLocked(bulkSyncMarker, "1", null, false, "aconfig"); + settingsState.insertSettingLocked(bulkSyncCounter, "1", null, false, + "com.google.android.platform.core_experiments_team_internal"); + requests = settingsState.handleBulkSyncToNewStorage(flags); + assertNull(requests); + value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); + assertEquals("1", value); } } @@ -1107,21 +1138,34 @@ public class SettingsStateTest { SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper()); Map<String, AconfigdFlagInfo> flags = new HashMap<>(); + String bulkSyncMarker = "aconfigd_marker/bulk_synced"; + String bulkSyncCounter = + "core_experiments_team_internal/" + + "BulkSyncTriggerCounterFlag__bulk_sync_trigger_counter"; synchronized (lock) { settingsState.insertSettingLocked("aconfigd_marker/bulk_synced", "true", null, false, "aconfig"); // when aconfigd is off, should change the marker to false ProtoOutputStream requests = settingsState.handleBulkSyncToNewStorage(flags); - assertTrue(requests == null); + assertNull(requests); String value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); - assertEquals("false", value); + assertEquals("0", value); // marker started with false value, after call, it should remain false requests = settingsState.handleBulkSyncToNewStorage(flags); - assertTrue(requests == null); + assertNull(requests); value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); - assertEquals("false", value); + assertEquals("0", value); + + // won't sync + settingsState.insertSettingLocked(bulkSyncMarker, "0", null, false, "aconfig"); + settingsState.insertSettingLocked(bulkSyncCounter, "1", null, false, + "com.google.android.platform.core_experiments_team_internal"); + requests = settingsState.handleBulkSyncToNewStorage(flags); + assertNull(requests); + value = settingsState.getSettingLocked("aconfigd_marker/bulk_synced").getValue(); + assertEquals("0", value); } } @@ -1164,6 +1208,7 @@ public class SettingsStateTest { .setFlagName("flag1") .setDefaultFlagValue("false") .setIsReadWrite(true) + .setNamespace("test_namespace") .build(); flagInfoDefault.put(flag1.getFullFlagName(), flag1); @@ -1186,6 +1231,7 @@ public class SettingsStateTest { .setFlagName("flag2") .setDefaultFlagValue("false") .setIsReadWrite(true) + .setNamespace("test_namespace") .build(); flagInfoDefault.put(flag2.getFullFlagName(), flag2); synchronized (lock) { @@ -1207,6 +1253,7 @@ public class SettingsStateTest { .setFlagName("flag3") .setDefaultFlagValue("false") .setIsReadWrite(false) + .setNamespace("test_namespace") .build(); flagInfoDefault.put(flag3.getFullFlagName(), flag3); synchronized (lock) { diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 90885ab51539..f5153e1aa8b3 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -217,6 +217,17 @@ flag { } flag { + name: "notification_group_hun_removal_animation_fix" + namespace: "systemui" + description: "Fix the lack of hun removal animation for group notifications" + "(not GROUP_ALERT_SUMMARY)" + bug: "343475993" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "scene_container" namespace: "systemui" description: "Enables the scene container framework go/flexiglass." @@ -1021,13 +1032,6 @@ flag { } flag { - name: "glanceable_hub_shortcut_button" - namespace: "systemui" - description: "Shows a button over the dream and lock screen to open the glanceable hub" - bug: "339667383" -} - -flag { name: "glanceable_hub_gesture_handle" namespace: "systemui" description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub" @@ -1118,3 +1122,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "translucent_occluding_activity_fix" + namespace: "systemui" + description: "Fixes occlusion animation for transluent activities" + bug: "303010980" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index b6e4e9b13a1c..c14ee6208176 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -22,6 +22,7 @@ import android.app.PendingIntent import android.app.TaskInfo import android.app.WindowConfiguration import android.content.ComponentName +import android.graphics.Color import android.graphics.Matrix import android.graphics.Rect import android.graphics.RectF @@ -53,6 +54,7 @@ import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.Flags.activityTransitionUseLargestWindow +import com.android.systemui.Flags.translucentOccludingActivityFix import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary import com.android.wm.shell.shared.IShellTransitions import com.android.wm.shell.shared.ShellTransitions @@ -991,7 +993,12 @@ constructor( controller.createAnimatorState() } val windowBackgroundColor = - window.taskInfo?.let { callback.getBackgroundColor(it) } ?: window.backgroundColor + if (translucentOccludingActivityFix() && window.isTranslucent) { + Color.TRANSPARENT + } else { + window.taskInfo?.let { callback.getBackgroundColor(it) } + ?: window.backgroundColor + } // TODO(b/184121838): We should somehow get the top and bottom radius of the window // instead of recomputing isExpandingFullyAbove here. diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index 2ed0f6c89ebd..e02e5f8af644 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -155,7 +155,7 @@ fun CommunalContainer( val coroutineScope = rememberCoroutineScope() val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle(CommunalScenes.Blank) - val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false) + val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle() val showGestureIndicator by viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false) val backgroundType by diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 9c2127c1a790..be51c1a76fad 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -484,6 +484,7 @@ private fun BoxScope.CommunalHubLazyGrid( rememberDragAndDropTargetState( gridState = gridState, contentListState = contentListState, + contentOffset = contentOffset, updateDragPositionForRemove = updateDragPositionForRemove ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt index 37fe7989cead..9e6f22a69dbc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt @@ -41,7 +41,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import com.android.systemui.communal.domain.model.CommunalContentModel -import com.android.systemui.communal.ui.compose.extensions.plus +import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset import com.android.systemui.communal.util.WidgetPickerIntentUtils import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent import kotlinx.coroutines.CoroutineScope @@ -57,6 +57,7 @@ import kotlinx.coroutines.launch @Composable internal fun rememberDragAndDropTargetState( gridState: LazyGridState, + contentOffset: Offset, contentListState: ContentListState, updateDragPositionForRemove: (offset: Offset) -> Boolean, ): DragAndDropTargetState { @@ -70,6 +71,7 @@ internal fun rememberDragAndDropTargetState( remember(gridState, contentListState) { DragAndDropTargetState( state = gridState, + contentOffset = contentOffset, contentListState = contentListState, scope = scope, autoScrollSpeed = autoScrollSpeed, @@ -145,6 +147,7 @@ internal fun Modifier.dragAndDropTarget( */ internal class DragAndDropTargetState( private val state: LazyGridState, + private val contentOffset: Offset, private val contentListState: ContentListState, private val scope: CoroutineScope, private val autoScrollSpeed: MutableState<Float>, @@ -214,8 +217,7 @@ internal class DragAndDropTargetState( return@let true } return false - } - ?: false + } ?: false } fun onEnded() { @@ -249,10 +251,9 @@ internal class DragAndDropTargetState( } private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? = - state.layoutInfo.visibleItemsInfo.firstOrNull { item -> - dragEvent.x.toInt() in item.offset.x..(item.offset + item.size).x && - dragEvent.y.toInt() in item.offset.y..(item.offset + item.size).y - } + state.layoutInfo.visibleItemsInfo.firstItemAtOffset( + Offset(dragEvent.x, dragEvent.y) - contentOffset + ) private fun movePlaceholderTo(index: Int) { val currentIndex = contentListState.list.indexOf(placeHolder) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt index 4555f13a1a2c..c34fb38f8558 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt @@ -19,9 +19,10 @@ package com.android.systemui.keyguard.ui.composable import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -33,12 +34,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.input.pointer.pointerInput import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel +import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture +import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel /** Container for lockscreen content that handles long-press to bring up the settings menu. */ @Composable +// TODO(b/344879669): now that it's more generic than long-press, rename it. fun LockscreenLongPress( - viewModel: KeyguardLongPressViewModel, + viewModel: KeyguardTouchHandlingViewModel, modifier: Modifier = Modifier, content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit, ) { @@ -50,14 +53,17 @@ fun LockscreenLongPress( Box( modifier = modifier - .combinedClickable( - enabled = isEnabled, - onLongClick = viewModel::onLongPress, - onClick = {}, - interactionSource = interactionSource, - // Passing null for the indication removes the ripple effect. - indication = null, - ) + .pointerInput(isEnabled) { + if (isEnabled) { + detectLongPressGesture { viewModel.onLongPress() } + } + } + .pointerInput(Unit) { + detectTapGestures( + onTap = { viewModel.onClick(it.x, it.y) }, + onDoubleTap = { viewModel.onDoubleClick() }, + ) + } .pointerInput(settingsMenuBounds) { awaitEachGesture { val pointerInputChange = awaitFirstDown() @@ -65,7 +71,9 @@ fun LockscreenLongPress( viewModel.onTouchedOutside() } } - }, + } + // Passing null for the indication removes the ripple effect. + .indication(interactionSource, null) ) { content(setSettingsMenuBounds) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt index 6b210af1308d..210ca69e6fd3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt @@ -43,7 +43,7 @@ constructor( @Composable override fun SceneScope.Content(modifier: Modifier) { LockscreenLongPress( - viewModel = viewModel.longPress, + viewModel = viewModel.touchHandling, modifier = modifier, ) { _ -> Box(modifier.background(Color.Black)) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index a39fa64dd45b..0a4c6fd21922 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -72,7 +72,7 @@ constructor( val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle() LockscreenLongPress( - viewModel = viewModel.longPress, + viewModel = viewModel.touchHandling, modifier = modifier, ) { onSettingsMenuPlaced -> Layout( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt index c83f62c4281c..065f2a2172b1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt @@ -74,7 +74,7 @@ constructor( val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle() LockscreenLongPress( - viewModel = viewModel.longPress, + viewModel = viewModel.touchHandling, modifier = modifier, ) { onSettingsMenuPlaced -> Layout( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt index 44b0535efb57..15032e00e6f7 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt @@ -30,8 +30,8 @@ import androidx.compose.ui.unit.toSize import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.isVisible import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder -import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper @@ -42,7 +42,7 @@ class SettingsMenuSection @Inject constructor( private val viewModel: KeyguardSettingsMenuViewModel, - private val longPressViewModel: KeyguardLongPressViewModel, + private val touchHandlingViewModel: KeyguardTouchHandlingViewModel, private val vibratorHelper: VibratorHelper, private val activityStarter: ActivityStarter, ) { @@ -69,7 +69,7 @@ constructor( KeyguardSettingsViewBinder.bind( view = this, viewModel = viewModel, - longPressViewModel = longPressViewModel, + touchHandlingViewModel = touchHandlingViewModel, rootViewModel = null, vibratorHelper = vibratorHelper, activityStarter = activityStarter, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 6805888214c2..2eea2f0565c0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -21,6 +21,7 @@ import android.util.Log import androidx.compose.animation.core.Animatable import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer @@ -233,6 +234,8 @@ fun SceneScope.NotificationScrollingStack( // The height of the scrim visible on screen when it is in its resting (collapsed) state. val minVisibleScrimHeight: () -> Float = { screenHeight - maxScrimTop() } + val isClickable by viewModel.isClickable.collectAsStateWithLifecycle() + // we are not scrolled to the top unless the scrim is at its maximum offset. LaunchedEffect(viewModel, scrimOffset) { snapshotFlow { scrimOffset.value >= 0f } @@ -328,6 +331,9 @@ fun SceneScope.NotificationScrollingStack( ) ) } + .thenIf(isClickable) { + Modifier.clickable(onClick = { viewModel.onEmptySpaceClicked() }) + } ) { // Creates a cutout in the background scrim in the shape of the notifications scrim. // Only visible when notif scrim alpha < 1, during shade expansion. diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt index 73a624a030fe..aca473d8eabc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt @@ -18,8 +18,9 @@ package com.android.systemui.qs.ui.composable import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -39,6 +40,7 @@ fun BrightnessMirror( viewModel: BrightnessMirrorViewModel, qsSceneAdapter: QSSceneAdapter, modifier: Modifier = Modifier, + measureFromContainer: Boolean = false, ) { val isShowing by viewModel.isShowing.collectAsStateWithLifecycle() val mirrorAlpha by @@ -47,9 +49,22 @@ fun BrightnessMirror( label = "alphaAnimationBrightnessMirrorShowing", ) val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsStateWithLifecycle() - val offset = IntOffset(0, mirrorOffsetAndSize.yOffset) + val yOffset = + if (measureFromContainer) { + mirrorOffsetAndSize.yOffsetFromContainer + } else { + mirrorOffsetAndSize.yOffsetFromWindow + } + val offset = IntOffset(0, yOffset) - Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = mirrorAlpha }) { + // Use unbounded=true as the full mirror (with paddings and background offset) may be larger + // than the space we have (but it will fit, because the brightness slider fits). + Box( + modifier = + modifier.fillMaxHeight().wrapContentWidth(unbounded = true).graphicsLayer { + alpha = mirrorAlpha + } + ) { QuickSettingsTheme { // The assumption for using this AndroidView is that there will be only one in view at // a given time (which is a reasonable assumption). Because `QSSceneAdapter` (actually diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index 0b57151e971d..2d5d25913074 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -185,7 +185,11 @@ private fun SceneScope.QuickSettingsScene( BrightnessMirror( viewModel = viewModel.brightnessMirrorViewModel, - qsSceneAdapter = viewModel.qsSceneAdapter + qsSceneAdapter = viewModel.qsSceneAdapter, + modifier = + Modifier.thenIf(cutoutLocation != CutoutLocation.CENTER) { + Modifier.displayCutoutPadding() + } ) val shouldPunchHoleBehindScrim = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt index 924aa540aa7f..4eaacf31e23d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt @@ -86,7 +86,7 @@ fun Session(storage: SessionStorage = SessionStorage()): Session = SessionImpl(s */ @Composable fun <T> Session.rememberSession(vararg inputs: Any?, key: String? = null, init: () -> T): T = - rememberSession(key, inputs, init = init) + rememberSession(key, *inputs, init = init) /** * An explicit storage for remembering composable state outside of the lifetime of a composition. @@ -151,7 +151,7 @@ fun rememberSaveableSession( vararg inputs: Any?, key: String? = null, ): SaveableSession = - rememberSaveable(inputs, SaveableSessionImpl.SessionSaver, key) { SaveableSessionImpl() } + rememberSaveable(*inputs, SaveableSessionImpl.SessionSaver, key) { SaveableSessionImpl() } private class SessionImpl( private val storage: SessionStorage = SessionStorage(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 22566e70f409..9c9e6c69de1c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -34,6 +34,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.observableTransitionState import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon import com.android.systemui.scene.shared.model.SceneDataSourceDelegator @@ -56,7 +58,6 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel * must have entries in this map. * @param modifier A modifier. */ -@OptIn(ExperimentalComposeUiApi::class) @Composable fun SceneContainer( viewModel: SceneContainerViewModel, @@ -66,8 +67,6 @@ fun SceneContainer( ) { val coroutineScope = rememberCoroutineScope() val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle() - val currentDestinations by - viewModel.currentDestinationScenes(coroutineScope).collectAsStateWithLifecycle() val state: MutableSceneTransitionLayoutState = remember { MutableSceneTransitionLayoutState( initialScene = currentSceneKey, @@ -88,20 +87,19 @@ fun SceneContainer( onDispose { viewModel.setTransitionState(null) } } + val userActionsBySceneKey: Map<SceneKey, Map<UserAction, UserActionResult>> = + sceneByKey.values.associate { scene -> + val userActions by scene.destinationScenes.collectAsStateWithLifecycle(emptyMap()) + val resolvedUserActions = viewModel.resolveSceneFamilies(userActions) + scene.key to resolvedUserActions + } + Box( modifier = Modifier.fillMaxSize(), ) { SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) { sceneByKey.forEach { (sceneKey, composableScene) -> - scene( - key = sceneKey, - userActions = - if (sceneKey == currentSceneKey) { - currentDestinations - } else { - viewModel.resolveSceneFamilies(composableScene.destinationScenes.value) - }, - ) { + scene(key = sceneKey, userActions = checkNotNull(userActionsBySceneKey[sceneKey])) { with(composableScene) { this@scene.Content( modifier = Modifier.element(sceneKey.rootElementKey).fillMaxSize(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index edef5fb2760a..4a6599a04fab 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -36,7 +36,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -97,6 +96,7 @@ import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView +import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.phone.ui.TintedIconManager @@ -138,6 +138,7 @@ constructor( private val shadeSession: SaveableSession, private val notificationStackScrollView: Lazy<NotificationScrollView>, private val viewModel: ShadeSceneViewModel, + private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, private val tintedIconManagerFactory: TintedIconManager.Factory, private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, private val statusBarIconController: StatusBarIconController, @@ -157,6 +158,7 @@ constructor( ShadeScene( notificationStackScrollView.get(), viewModel = viewModel, + notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, createTintedIconManager = tintedIconManagerFactory::create, createBatteryMeterViewController = batteryMeterViewControllerFactory::create, statusBarIconController = statusBarIconController, @@ -177,6 +179,7 @@ constructor( private fun SceneScope.ShadeScene( notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, + notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, statusBarIconController: StatusBarIconController, @@ -191,6 +194,7 @@ private fun SceneScope.ShadeScene( SingleShade( notificationStackScrollView = notificationStackScrollView, viewModel = viewModel, + notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, @@ -203,6 +207,7 @@ private fun SceneScope.ShadeScene( SplitShade( notificationStackScrollView = notificationStackScrollView, viewModel = viewModel, + notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, @@ -219,6 +224,7 @@ private fun SceneScope.ShadeScene( private fun SceneScope.SingleShade( notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, + notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, statusBarIconController: StatusBarIconController, @@ -330,7 +336,7 @@ private fun SceneScope.SingleShade( NotificationScrollingStack( shadeSession = shadeSession, stackScrollView = notificationStackScrollView, - viewModel = viewModel.notifications, + viewModel = notificationsPlaceholderViewModel, maxScrimTop = { maxNotifScrimTop.value }, shadeMode = ShadeMode.Single, shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim, @@ -354,7 +360,7 @@ private fun SceneScope.SingleShade( } NotificationStackCutoffGuideline( stackScrollView = notificationStackScrollView, - viewModel = viewModel.notifications, + viewModel = notificationsPlaceholderViewModel, modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding() ) } @@ -364,6 +370,7 @@ private fun SceneScope.SingleShade( private fun SceneScope.SplitShade( notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, + notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, statusBarIconController: StatusBarIconController, @@ -431,8 +438,10 @@ private fun SceneScope.SplitShade( label = "alphaAnimationBrightnessMirrorContentHiding", ) - viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha) - DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } } + notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(contentAlpha) + DisposableEffect(Unit) { + onDispose { notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(1f) } + } val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle() @@ -474,9 +483,9 @@ private fun SceneScope.SplitShade( BrightnessMirror( viewModel = viewModel.brightnessMirrorViewModel, qsSceneAdapter = viewModel.qsSceneAdapter, - // Need to remove the offset of the header height, as the mirror uses - // the position of the Brightness slider in the window - modifier = Modifier.offset(y = -ShadeHeader.Dimensions.CollapsedHeight) + // Need to use the offset measured from the container as the header + // has to be accounted for + measureFromContainer = true ) Column( verticalArrangement = Arrangement.Top, @@ -533,7 +542,7 @@ private fun SceneScope.SplitShade( NotificationScrollingStack( shadeSession = shadeSession, stackScrollView = notificationStackScrollView, - viewModel = viewModel.notifications, + viewModel = notificationsPlaceholderViewModel, maxScrimTop = { 0f }, shouldPunchHoleBehindScrim = false, shouldReserveSpaceForNavBar = false, @@ -548,7 +557,7 @@ private fun SceneScope.SplitShade( } NotificationStackCutoffGuideline( stackScrollView = notificationStackScrollView, - viewModel = viewModel.notifications, + viewModel = notificationsPlaceholderViewModel, modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding() ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java index 630bcd6beacc..7ebc224a00db 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -49,6 +50,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.ambient.touch.scrim.ScrimController; import com.android.systemui.ambient.touch.scrim.ScrimManager; import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.FakeUserTracker; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shared.system.InputChannelCompat; @@ -113,6 +115,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { LockPatternUtils mLockPatternUtils; @Mock + ActivityStarter mActivityStarter; + + @Mock Region mRegion; @Captor @@ -148,7 +153,8 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { mFlingAnimationUtilsClosing, TOUCH_REGION, MIN_BOUNCER_HEIGHT, - mUiEventLogger); + mUiEventLogger, + mActivityStarter); when(mScrimManager.getCurrentController()).thenReturn(mScrimController); when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator); @@ -397,7 +403,12 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { .isTrue(); // We should not expand since the keyguard is not secure verify(mScrimController, never()).expand(any()); - // Since we are swiping up, we should wake from dreams. + + // Since we are swiping up, we should dismiss the keyguard and wake from dreams. + ArgumentCaptor<Runnable> dismissKeyguardRunnable = ArgumentCaptor.forClass(Runnable.class); + verify(mActivityStarter).executeRunnableDismissingKeyguard( + dismissKeyguardRunnable.capture(), isNull(), eq(true), eq(true), eq(false)); + dismissKeyguardRunnable.getValue().run(); verify(mCentralSurfaces).awakenDreams(); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 60b48f28fc23..242e822664c0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -324,7 +324,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { fun showUdfpsOverlay_awake() = testScope.runTest { withReason(REASON_AUTH_KEYGUARD) { - mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) powerRepository.updateWakefulness( rawState = WakefulnessState.AWAKE, lastWakeReason = WakeSleepReason.POWER_BUTTON, @@ -341,7 +340,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { fun showUdfpsOverlay_whileGoingToSleep() = testScope.runTest { withReasonSuspend(REASON_AUTH_KEYGUARD) { - mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.OFF, to = KeyguardState.GONE, @@ -370,7 +368,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { fun showUdfpsOverlay_whileAsleep() = testScope.runTest { withReasonSuspend(REASON_AUTH_KEYGUARD) { - mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.OFF, to = KeyguardState.GONE, @@ -399,7 +396,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { fun neverRemoveViewThatHasNotBeenAdded() = testScope.runTest { withReasonSuspend(REASON_AUTH_KEYGUARD) { - mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) controllerOverlay.show(udfpsController, overlayParams) val view = controllerOverlay.getTouchOverlay() view?.let { @@ -414,7 +410,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { fun showUdfpsOverlay_afterFinishedTransitioningToAOD() = testScope.runTest { withReasonSuspend(REASON_AUTH_KEYGUARD) { - mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.OFF, to = KeyguardState.GONE, @@ -542,7 +537,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { fun addViewPending_layoutIsNotUpdated() = testScope.runTest { withReasonSuspend(REASON_AUTH_KEYGUARD) { - mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) // GIVEN going to sleep @@ -580,7 +574,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { fun updateOverlayParams_viewLayoutUpdated() = testScope.runTest { withReasonSuspend(REASON_AUTH_KEYGUARD) { - mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE) powerRepository.updateWakefulness( rawState = WakefulnessState.AWAKE, lastWakeReason = WakeSleepReason.POWER_BUTTON, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt index fe683e07a93d..dcc9c7aacdf0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt @@ -47,6 +47,7 @@ import org.mockito.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @SmallTest +@EnableFlags(Flags.FLAG_COMMUNAL_HUB) @RunWith(AndroidJUnit4::class) class CommunalDreamStartableTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -76,7 +77,7 @@ class CommunalDreamStartableTest : SysuiTestCase() { testScope.runTest { keyguardRepository.setDreaming(false) powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON) - whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true) + whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true) runCurrent() verify(dreamManager, never()).startDream() @@ -92,7 +93,7 @@ class CommunalDreamStartableTest : SysuiTestCase() { testScope.runTest { keyguardRepository.setDreaming(false) powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON) - whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true) + whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true) runCurrent() verify(dreamManager, never()).startDream() @@ -118,7 +119,7 @@ class CommunalDreamStartableTest : SysuiTestCase() { keyguardRepository.setDreaming(false) powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON) // Not eligible to dream - whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(false) + whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(false) transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB) verify(dreamManager, never()).startDream() @@ -129,7 +130,7 @@ class CommunalDreamStartableTest : SysuiTestCase() { testScope.runTest { keyguardRepository.setDreaming(true) powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON) - whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true) + whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true) transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB) verify(dreamManager, never()).startDream() @@ -140,7 +141,7 @@ class CommunalDreamStartableTest : SysuiTestCase() { testScope.runTest { keyguardRepository.setDreaming(true) powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON) - whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true) + whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true) // Verify we do not trigger dreaming for any other state besides glanceable hub for (state in KeyguardState.entries) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt index fb2b33d70c47..da40f640d5fa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt @@ -20,7 +20,6 @@ import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL import android.app.admin.devicePolicyManager -import android.appwidget.AppWidgetProviderInfo import android.content.Intent import android.content.pm.UserInfo import android.os.UserManager.USER_TYPE_PROFILE_MANAGED @@ -29,7 +28,6 @@ import android.platform.test.annotations.EnableFlags import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.settingslib.flags.Flags.FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher @@ -183,42 +181,6 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { ) } - @EnableFlags(FLAG_COMMUNAL_HUB) - @Test - fun hubShowsWidgetCategoriesSetByUser() = - testScope.runTest { - kosmos.fakeSettings.putIntForUser( - CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING, - AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, - PRIMARY_USER.id - ) - val setting by collectLastValue(underTest.getWidgetCategories(PRIMARY_USER)) - assertThat(setting?.categories) - .isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN) - } - - @EnableFlags(FLAG_COMMUNAL_HUB) - @DisableFlags(FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT) - @Test - fun hubShowsKeyguardWidgetsByDefault() = - testScope.runTest { - val setting by collectLastValue(underTest.getWidgetCategories(PRIMARY_USER)) - assertThat(setting?.categories) - .isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) - } - - @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT) - @Test - fun hubShowsAllWidgetsByDefaultWhenFlagEnabled() = - testScope.runTest { - val setting by collectLastValue(underTest.getWidgetCategories(PRIMARY_USER)) - assertThat(setting?.categories) - .isEqualTo( - AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD + - AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN - ) - } - @Test fun backgroundType_defaultValue() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index d951cca89f64..7b26db50814e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -36,7 +36,6 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher -import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository @@ -81,7 +80,6 @@ import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -915,14 +913,6 @@ class CommunalInteractorTest : SysuiTestCase() { ) runCurrent() - // Keyguard widgets are allowed. - kosmos.fakeSettings.putIntForUser( - CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING, - AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD, - mainUser.id - ) - runCurrent() - // When work profile is paused. whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id)))) .thenReturn(true) @@ -956,93 +946,6 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun widgetContent_containsDisabledWidgets_whenCategoryNotAllowed() = - testScope.runTest { - // Communal available, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - - val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set( - userInfos = userInfos, - selectedUserIndex = 0, - ) - userRepository.setSelectedUserInfo(MAIN_USER_INFO) - runCurrent() - - // Widgets available. - val widget1 = - createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN) - val widget2 = - createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) - val widget3 = - createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) - val widgets = listOf(widget1, widget2, widget3) - widgetRepository.setCommunalWidgets(widgets) - - val widgetContent by collectLastValue(underTest.widgetContent) - kosmos.fakeSettings.putIntForUser( - CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING, - AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD, - mainUser.id - ) - - // Only the keyguard widget is enabled. - assertThat(widgetContent).hasSize(3) - assertThat(widgetContent!!.get(0)) - .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java) - assertThat(widgetContent!!.get(1)) - .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java) - assertThat(widgetContent!!.get(2)) - .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java) - } - - @Test - fun widgetContent_allEnabled_whenCategoryAllowed() = - testScope.runTest { - // Communal available, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - - val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set( - userInfos = userInfos, - selectedUserIndex = 0, - ) - userRepository.setSelectedUserInfo(MAIN_USER_INFO) - runCurrent() - - // Widgets available. - val widget1 = - createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN) - val widget2 = - createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) - val widget3 = - createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) - val widgets = listOf(widget1, widget2, widget3) - widgetRepository.setCommunalWidgets(widgets) - - val widgetContent by collectLastValue(underTest.widgetContent) - kosmos.fakeSettings.putIntForUser( - CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING, - AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or - AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN, - mainUser.id - ) - - // All widgets are enabled. - assertThat(widgetContent).hasSize(3) - widgetContent!!.forEach { model -> - assertThat(model) - .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java) - } - } - - @Test fun filterWidgets_whenDisallowedByDevicePolicyForWorkProfile() = testScope.runTest { // Keyguard showing, and tutorial completed. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 51991dec39ff..2694cab09ab2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -153,6 +153,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { CommunalViewModel( kosmos.testDispatcher, testScope, + kosmos.testScope.backgroundScope, context.resources, kosmos.keyguardTransitionInteractor, kosmos.keyguardInteractor, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt index 7b7d03b2024a..70448955eff0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt @@ -72,6 +72,7 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { /* animationController = */ notNull(), /* fillInIntent = */ refEq(fillInIntent), /* extraOptions = */ refEq(activityOptions.toBundle()), + /* customMessage */ isNull(), ) } @@ -93,6 +94,7 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { /* animationController = */ isNull(), /* fillInIntent = */ refEq(fillInIntent), /* extraOptions = */ refEq(activityOptions.toBundle()), + /* customMessage */ isNull(), ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt index 74eee9b24cbc..7d0f040793d8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt @@ -274,6 +274,29 @@ class QSLongPressEffectTest : SysuiTestCase() { assertThat(couldClick).isFalse() } + @Test + fun onTileClick_whileIdle_withQSTile_clicks() = + testWhileInState(QSLongPressEffect.State.IDLE) { + // GIVEN that a click was detected + val couldClick = longPressEffect.onTileClick() + + // THEN the click is successful + assertThat(couldClick).isTrue() + } + + @Test + fun onTileClick_whileIdle_withoutQSTile_cannotClick() = + testWhileInState(QSLongPressEffect.State.IDLE) { + // GIVEN that no QSTile has been set + longPressEffect.qsTile = null + + // GIVEN that a click was detected + val couldClick = longPressEffect.onTileClick() + + // THEN the click is not successful + assertThat(couldClick).isFalse() + } + private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) = with(kosmos) { testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt index 49d039970a24..26fcb234843d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt @@ -90,7 +90,6 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = FakeUserTracker(), - systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) settings = FakeSettings() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt index 9ab1ac116b0d..0f3e78b1fd7f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt @@ -29,7 +29,6 @@ import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -81,7 +80,6 @@ class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() { context = context, userFileManager = userFileManager, userTracker = userTracker, - systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt index 159ce36bea2d..8e109b4aab20 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt @@ -91,7 +91,6 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, - systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) client1 = FakeCustomizationProviderClient() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 78a116737349..2d77f4f1c436 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -148,7 +148,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, - systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt index 9d34903d544d..96b4b4325408 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepos import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.testScope import com.android.systemui.res.R +import com.android.systemui.shade.pulsingGestureListener import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock @@ -53,14 +54,14 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) -class KeyguardLongPressInteractorTest : SysuiTestCase() { +class KeyguardTouchHandlingInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().apply { this.accessibilityManagerWrapper = mock<AccessibilityManagerWrapper>() this.uiEventLogger = mock<UiEventLoggerFake>() } - private lateinit var underTest: KeyguardLongPressInteractor + private lateinit var underTest: KeyguardTouchHandlingInteractor private val logger = kosmos.uiEventLogger private val testScope = kosmos.testScope @@ -209,7 +210,7 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { underTest.onLongPress() assertThat(isMenuVisible).isTrue() - advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) + advanceTimeBy(KeyguardTouchHandlingInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) assertThat(isMenuVisible).isFalse() } @@ -224,11 +225,11 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { assertThat(isMenuVisible).isTrue() underTest.onMenuTouchGestureStarted() - advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) + advanceTimeBy(KeyguardTouchHandlingInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) assertThat(isMenuVisible).isTrue() underTest.onMenuTouchGestureEnded(/* isClick= */ false) - advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) + advanceTimeBy(KeyguardTouchHandlingInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) assertThat(isMenuVisible).isFalse() } @@ -241,7 +242,7 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { underTest.onLongPress() verify(logger) - .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN) + .log(KeyguardTouchHandlingInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN) } @Test @@ -254,7 +255,7 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { underTest.onMenuTouchGestureEnded(/* isClick= */ true) verify(logger) - .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED) + .log(KeyguardTouchHandlingInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED) } @Test @@ -288,7 +289,7 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { // This needs to be re-created for each test outside of kosmos since the flag values are // read during initialization to set up flows. Maybe there is a better way to handle that. underTest = - KeyguardLongPressInteractor( + KeyguardTouchHandlingInteractor( appContext = mContext, scope = testScope.backgroundScope, transitionInteractor = kosmos.keyguardTransitionInteractor, @@ -300,7 +301,8 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled) }, broadcastDispatcher = fakeBroadcastDispatcher, - accessibilityManager = kosmos.accessibilityManagerWrapper + accessibilityManager = kosmos.accessibilityManagerWrapper, + pulsingGestureListener = kosmos.pulsingGestureListener, ) setUpState() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 61d8216c87c2..4eb146dbbaba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.platform.test.annotations.EnableFlags +import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.SceneKey @@ -61,6 +62,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) +@RunWithLooper @EnableSceneContainer class LockscreenSceneViewModelTest : SysuiTestCase() { @@ -265,8 +267,8 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, deviceEntryInteractor = kosmos.deviceEntryInteractor, communalInteractor = kosmos.communalInteractor, - longPress = - KeyguardLongPressViewModel( + touchHandling = + KeyguardTouchHandlingViewModel( interactor = mock(), ), notifications = kosmos.notificationsPlaceholderViewModel, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt new file mode 100644 index 000000000000..1c3021ef5839 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.compose.runtime.mutableStateOf +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DragAndDropStateTest : SysuiTestCase() { + private val listState = EditTileListState(TestEditTiles) + private val underTest = DragAndDropState(mutableStateOf(null), listState) + + @Test + fun isMoving_returnsCorrectValue() { + // Asserts no tiles is moving + TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() } + + // Start the drag movement + val movingTileSpec = TestEditTiles[0].tileSpec + underTest.onStarted(movingTileSpec) + + // Assert that the correct tile is marked as moving + TestEditTiles.forEach { + assertThat(underTest.isMoving(it.tileSpec)).isEqualTo(movingTileSpec == it.tileSpec) + } + } + + @Test + fun onMoved_updatesList() { + val movingTileSpec = TestEditTiles[0].tileSpec + + // Start the drag movement + underTest.onStarted(movingTileSpec) + + // Move the tile to the end of the list + underTest.onMoved(listState.tiles[5].tileSpec) + assertThat(underTest.currentPosition()).isEqualTo(5) + + // Move the tile to the middle of the list + underTest.onMoved(listState.tiles[2].tileSpec) + assertThat(underTest.currentPosition()).isEqualTo(2) + } + + @Test + fun onDrop_resetsMovingTile() { + val movingTileSpec = TestEditTiles[0].tileSpec + + // Start the drag movement + underTest.onStarted(movingTileSpec) + + // Move the tile to the end of the list + underTest.onMoved(listState.tiles[5].tileSpec) + + // Drop the tile + underTest.onDrop() + + // Asserts no tiles is moving + TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() } + } + + companion object { + private fun createEditTile(tileSpec: String): EditTileViewModel { + return EditTileViewModel( + tileSpec = TileSpec.create(tileSpec), + icon = Icon.Resource(0, null), + label = Text.Loaded("unused"), + appName = null, + isCurrent = true, + availableEditActions = emptySet(), + ) + } + + private val TestEditTiles = + listOf( + createEditTile("tileA"), + createEditTile("tileB"), + createEditTile("tileC"), + createEditTile("tileD"), + createEditTile("tileE"), + createEditTile("tileF"), + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt new file mode 100644 index 000000000000..517b60168fbf --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class EditTileListStateTest : SysuiTestCase() { + val underTest = EditTileListState(TestEditTiles) + + @Test + fun movingNonExistentTile_listUnchanged() { + underTest.move(TileSpec.create("other_tile"), TestEditTiles[0].tileSpec) + + assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray()) + } + + @Test + fun movingTileToNonExistentTarget_listUnchanged() { + underTest.move(TestEditTiles[0].tileSpec, TileSpec.create("other_tile")) + + assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray()) + } + + @Test + fun movingTileToItself_listUnchanged() { + underTest.move(TestEditTiles[0].tileSpec, TestEditTiles[0].tileSpec) + + assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray()) + } + + @Test + fun movingTileToSameSection_listUpdates() { + // Move tile at index 0 to index 1. Tile 0 should remain current. + underTest.move(TestEditTiles[0].tileSpec, TestEditTiles[1].tileSpec) + + // Assert the tiles 0 and 1 have changed places. + assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1]) + assertThat(underTest.tiles[1]).isEqualTo(TestEditTiles[0]) + + // Assert the rest of the list is unchanged + assertThat(underTest.tiles.subList(2, 5)) + .containsExactly(*TestEditTiles.subList(2, 5).toTypedArray()) + } + + @Test + fun movingTileToDifferentSection_listAndTileUpdates() { + // Move tile at index 0 to index 3. Tile 0 should no longer be current. + underTest.move(TestEditTiles[0].tileSpec, TestEditTiles[3].tileSpec) + + // Assert tile 0 is now at index 3 and is no longer current. + assertThat(underTest.tiles[3]).isEqualTo(TestEditTiles[0].copy(isCurrent = false)) + + // Assert previous tiles have shifted places + assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1]) + assertThat(underTest.tiles[1]).isEqualTo(TestEditTiles[2]) + assertThat(underTest.tiles[2]).isEqualTo(TestEditTiles[3]) + + // Assert the rest of the list is unchanged + assertThat(underTest.tiles.subList(4, 5)) + .containsExactly(*TestEditTiles.subList(4, 5).toTypedArray()) + } + + companion object { + private fun createEditTile(tileSpec: String, isCurrent: Boolean): EditTileViewModel { + return EditTileViewModel( + tileSpec = TileSpec.create(tileSpec), + icon = Icon.Resource(0, null), + label = Text.Loaded("unused"), + appName = null, + isCurrent = isCurrent, + availableEditActions = emptySet(), + ) + } + + private val TestEditTiles = + listOf( + createEditTile("tileA", true), + createEditTile("tileB", true), + createEditTile("tileC", true), + createEditTile("tileD", false), + createEditTile("tileE", false), + createEditTile("tileF", false), + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt index 9ce2e0f87499..c33e2a49ef5d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt @@ -92,7 +92,8 @@ class QSTileViewModelTest : SysuiTestCase() { runCurrent() assertThat(states()).isNotEmpty() - assertThat(states().first().label).isEqualTo(testTileData) + assertThat(states().last()).isNotNull() + assertThat(states().last()!!.label).isEqualTo(testTileData) verify(qsTileLogger).logInitialRequest(eq(tileConfig.tileSpec)) } @@ -196,6 +197,7 @@ class QSTileViewModelTest : SysuiTestCase() { qsTileLogger, FakeSystemClock(), testCoroutineDispatcher, + testCoroutineDispatcher, scope.backgroundScope, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt index 6066d24c2214..7955f2fc1335 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt @@ -256,6 +256,7 @@ class QSTileViewModelUserInputTest : SysuiTestCase() { qsTileLogger, FakeSystemClock(), testCoroutineDispatcher, + testCoroutineDispatcher, scope.backgroundScope, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index e01ffa655a1e..924962187ced 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.ui.viewmodel +import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.Back @@ -65,6 +66,7 @@ import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidJUnit4::class) +@RunWithLooper @EnableSceneContainer class QuickSettingsSceneViewModelTest : SysuiTestCase() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 412505d50fc9..39b366226987 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -48,16 +48,13 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.kosmos.testScope -import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.powerInteractor -import com.android.systemui.qs.footerActionsController -import com.android.systemui.qs.footerActionsViewModelFactory -import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter +import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver import com.android.systemui.scene.domain.startable.sceneContainerStartable @@ -65,16 +62,14 @@ import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel -import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel -import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModel +import com.android.systemui.shade.ui.viewmodel.shadeSceneViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.telephony.data.repository.fakeTelephonyRepository import com.android.systemui.testKosmos -import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -138,7 +133,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneInteractor = sceneInteractor, falsingInteractor = kosmos.falsingInteractor, powerInteractor = kosmos.powerInteractor, - scenes = kosmos.scenes, ) .apply { setTransitionState(transitionState) } } @@ -152,8 +146,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, deviceEntryInteractor = deviceEntryInteractor, communalInteractor = communalInteractor, - longPress = - KeyguardLongPressViewModel( + touchHandling = + KeyguardTouchHandlingViewModel( interactor = mock(), ), notifications = kosmos.notificationsPlaceholderViewModel, @@ -167,7 +161,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { private var bouncerSceneJob: Job? = null - private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { mock() }) + private val qsFlexiglassAdapter = kosmos.fakeQSSceneAdapter private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager private lateinit var telecomManager: TelecomManager @@ -200,20 +194,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor bouncerViewModel = kosmos.bouncerViewModel - shadeSceneViewModel = - ShadeSceneViewModel( - applicationScope = testScope.backgroundScope, - shadeHeaderViewModel = kosmos.shadeHeaderViewModel, - qsSceneAdapter = qsFlexiglassAdapter, - notifications = kosmos.notificationsPlaceholderViewModel, - brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel, - mediaCarouselInteractor = kosmos.mediaCarouselInteractor, - shadeInteractor = kosmos.shadeInteractor, - footerActionsController = kosmos.footerActionsController, - footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, - sceneInteractor = sceneInteractor, - unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor, - ) + shadeSceneViewModel = kosmos.shadeSceneViewModel val startable = kosmos.sceneContainerStartable startable.start() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index 5c30379ea2fb..ea95aab4a1c4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -28,10 +28,8 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.fakeScenes import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.sceneKeys -import com.android.systemui.scene.scenes import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.testKosmos @@ -66,7 +64,6 @@ class SceneContainerViewModelTest : SysuiTestCase() { sceneInteractor = sceneInteractor, falsingInteractor = kosmos.falsingInteractor, powerInteractor = kosmos.powerInteractor, - scenes = kosmos.scenes, ) } @@ -217,23 +214,4 @@ class SceneContainerViewModelTest : SysuiTestCase() { assertThat(isVisible).isFalse() } - - @Test - fun currentDestinationScenes_onlyTheCurrentSceneIsCollected() = - testScope.runTest { - val unused by collectLastValue(underTest.currentDestinationScenes(backgroundScope)) - val currentScene by collectLastValue(sceneInteractor.currentScene) - kosmos.fakeScenes.forEach { scene -> - fakeSceneDataSource.changeScene(toScene = scene.key) - runCurrent() - assertThat(currentScene).isEqualTo(scene.key) - - assertThat(scene.isDestinationScenesBeingCollected).isTrue() - kosmos.fakeScenes - .filter { it.key != scene.key } - .forEach { otherScene -> - assertThat(otherScene.isDestinationScenesBeingCollected).isFalse() - } - } - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt index 6de7f403a745..13b61bcae6ce 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt @@ -68,4 +68,27 @@ class BrightnessMirrorInflaterTest : SysuiTestCase() { Assert.setTestThread(null) } + + @Test + fun inflate_frameHasPadding() { + Assert.setTestThread(Thread.currentThread()) + + val (frame, _) = + BrightnessMirrorInflater.inflate( + themedContext, + kosmos.brightnessSliderControllerFactory, + ) + + assertThat(frame.visibility).isEqualTo(View.VISIBLE) + + val padding = + context.resources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding) + + assertThat(frame.paddingLeft).isEqualTo(padding) + assertThat(frame.paddingTop).isEqualTo(padding) + assertThat(frame.paddingRight).isEqualTo(padding) + assertThat(frame.paddingBottom).isEqualTo(padding) + + Assert.setTestThread(null) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt index 09fc6f9ad96d..90c11d466f80 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt @@ -16,10 +16,9 @@ package com.android.systemui.settings.brightness.ui.viewmodel -import android.content.applicationContext import android.content.res.mainResources -import android.view.ContextThemeWrapper import android.view.View +import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -27,12 +26,10 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor -import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel import com.android.systemui.settings.brightness.ui.viewModel.LocationAndSize import com.android.systemui.settings.brightnessSliderControllerFactory import com.android.systemui.testKosmos -import com.android.systemui.util.Assert import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -47,9 +44,6 @@ class BrightnessMirrorViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() - private val themedContext = - ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings) - private val underTest = with(kosmos) { BrightnessMirrorViewModel( @@ -76,7 +70,7 @@ class BrightnessMirrorViewModelTest : SysuiTestCase() { } @Test - fun setLocationInWindow_correctLocationAndSize() = + fun locationInWindowAndContainer_correctLocationAndSize() = with(kosmos) { testScope.runTest { val locationAndSize by collectLastValue(underTest.locationAndSize) @@ -101,6 +95,7 @@ class BrightnessMirrorViewModelTest : SysuiTestCase() { whenever(measuredHeight).thenReturn(height) whenever(measuredWidth).thenReturn(width) } + val yOffsetFromContainer = setContainerViewHierarchy(mockView) underTest.setLocationAndSize(mockView) @@ -108,7 +103,8 @@ class BrightnessMirrorViewModelTest : SysuiTestCase() { .isEqualTo( // Adjust for padding around the view LocationAndSize( - yOffset = y - padding, + yOffsetFromWindow = y - padding, + yOffsetFromContainer = yOffsetFromContainer - padding, width = width + 2 * padding, height = height + 2 * padding, ) @@ -116,31 +112,20 @@ class BrightnessMirrorViewModelTest : SysuiTestCase() { } } - @Test - fun setLocationInWindow_paddingSetToRootView() = - with(kosmos) { - Assert.setTestThread(Thread.currentThread()) - val padding = - mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding) - - val view = mock<View>() - - val (_, sliderController) = - BrightnessMirrorInflater.inflate( - themedContext, - brightnessSliderControllerFactory, - ) - underTest.setToggleSlider(sliderController) - - underTest.setLocationAndSize(view) - - with(sliderController.rootView) { - assertThat(paddingBottom).isEqualTo(padding) - assertThat(paddingTop).isEqualTo(padding) - assertThat(paddingLeft).isEqualTo(padding) - assertThat(paddingRight).isEqualTo(padding) - } + private fun setContainerViewHierarchy(mockView: View): Int { + val rootView = FrameLayout(context) + val containerView = FrameLayout(context).apply { id = R.id.quick_settings_container } + val otherView = FrameLayout(context) - Assert.setTestThread(null) - } + rootView.addView(containerView) + containerView.addView(otherView) + otherView.addView(mockView) + + containerView.setLeftTopRightBottom(1, /* top= */ 1, 1, 1) + otherView.setLeftTopRightBottom(0, /* top= */ 2, 0, 0) + whenever(mockView.parent).thenReturn(otherView) + whenever(mockView.top).thenReturn(3) + + return 2 + 3 + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index a0295c9f36b0..673d5ef5d962 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -36,28 +36,20 @@ import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.data.repository.mediaFilterRepository -import com.android.systemui.media.controls.domain.pipeline.MediaDataManager -import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor import com.android.systemui.media.controls.shared.model.MediaData -import com.android.systemui.qs.footerActionsController -import com.android.systemui.qs.footerActionsViewModelFactory -import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter +import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter +import com.android.systemui.qs.ui.adapter.qsSceneAdapter import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade -import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel import com.android.systemui.shade.data.repository.shadeRepository -import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.domain.startable.shadeStartable import com.android.systemui.shade.shared.model.ShadeMode -import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel import com.android.systemui.testKosmos -import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider -import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import java.util.Locale import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -66,11 +58,8 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.test.TestScope 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.Mock -import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -83,32 +72,9 @@ class ShadeSceneViewModelTest : SysuiTestCase() { private val testScope = kosmos.testScope private val sceneInteractor by lazy { kosmos.sceneInteractor } private val shadeRepository by lazy { kosmos.shadeRepository } + private val qsSceneAdapter by lazy { kosmos.fakeQSSceneAdapter } - private val qsSceneAdapter = FakeQSSceneAdapter({ mock() }) - - private lateinit var underTest: ShadeSceneViewModel - - @Mock private lateinit var mediaDataManager: MediaDataManager - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - underTest = - ShadeSceneViewModel( - applicationScope = testScope.backgroundScope, - shadeHeaderViewModel = kosmos.shadeHeaderViewModel, - qsSceneAdapter = qsSceneAdapter, - notifications = kosmos.notificationsPlaceholderViewModel, - brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel, - mediaCarouselInteractor = kosmos.mediaCarouselInteractor, - shadeInteractor = kosmos.shadeInteractor, - footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, - footerActionsController = kosmos.footerActionsController, - sceneInteractor = kosmos.sceneInteractor, - unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor, - ) - } + private val underTest: ShadeSceneViewModel by lazy { kosmos.shadeSceneViewModel } @Test fun upTransitionSceneKey_deviceLocked_lockScreen() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt index cc3fdc592b45..23b28e37a4db 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification +import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState @@ -48,6 +49,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) +@RunWithLooper @EnableSceneContainer class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { @@ -171,6 +173,22 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { } @Test + fun shadeExpansion_idleOnQs() = + testScope.runTest { + val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(currentScene = Scenes.QuickSettings) + ) + sceneInteractor.setTransitionState(transitionState) + val expandFraction by collectLastValue(scrollViewModel.expandFraction) + assertThat(expandFraction).isEqualTo(1f) + + fakeSceneDataSource.changeScene(toScene = Scenes.QuickSettings) + val isScrollable by collectLastValue(scrollViewModel.isScrollable) + assertThat(isScrollable).isFalse() + } + + @Test fun shadeExpansion_shadeToQs() = testScope.runTest { val transitionState = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt index ee9fd3494d96..4944c8bd0221 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel +import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -31,9 +32,10 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) +@RunWithLooper class NotificationsPlaceholderViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() - private val underTest = kosmos.notificationsPlaceholderViewModel + private val underTest by lazy { kosmos.notificationsPlaceholderViewModel } @Test fun onBoundsChanged() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt index 5887f90036ec..ccd78ee82169 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt @@ -154,6 +154,25 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { } @Test + fun startPendingIntentDismissingKeyguard_withCustomMessage_dismissWithAction() { + val pendingIntent = mock(PendingIntent::class.java) + `when`(pendingIntent.isActivity).thenReturn(true) + `when`(keyguardStateController.isShowing).thenReturn(true) + `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) + val customMessage = "Custom unlock reason" + + underTest.startPendingIntentDismissingKeyguard( + intent = pendingIntent, + dismissShade = true, + customMessage = customMessage + ) + mainExecutor.runAllReady() + + verify(statusBarKeyguardViewManager) + .dismissWithAction(any(), eq(null), anyBoolean(), eq(customMessage)) + } + + @Test fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLs_launchAnimator() { val pendingIntent = mock(PendingIntent::class.java) val parent = FrameLayout(context) @@ -466,6 +485,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { animationController: ActivityTransitionAnimator.Controller?, fillInIntent: Intent? = null, extraOptions: Bundle? = null, + customMessage: String? = null, ) { underTest.startPendingIntentDismissingKeyguard( intent = intent, @@ -475,6 +495,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { showOverLockscreen = true, fillInIntent = fillInIntent, extraOptions = extraOptions, + customMessage = customMessage, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java index 200e92e4370b..7346323f733d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java @@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when; import android.content.Context; +import android.os.Handler; import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper; @@ -85,6 +86,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { @Mock private DumpManager dumpManager; private AvalancheController mAvalancheController; + @Mock private Handler mBgHandler; + private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone { TestableHeadsUpManagerPhone( Context context, @@ -101,7 +104,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { UiEventLogger uiEventLogger, JavaAdapter javaAdapter, ShadeInteractor shadeInteractor, - AvalancheController avalancheController + AvalancheController avalancheController, + Handler bgHandler ) { super( context, @@ -119,7 +123,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { uiEventLogger, javaAdapter, shadeInteractor, - avalancheController + avalancheController, + bgHandler ); mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; mAutoDismissTime = TEST_AUTO_DISMISS_TIME; @@ -142,7 +147,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { mUiEventLogger, mJavaAdapter, mShadeInteractor, - mAvalancheController + mAvalancheController, + mBgHandler ); } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 7cf56aa5c40e..abb721ab0dd8 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -84,14 +84,17 @@ public interface ActivityStarter { * Similar to {@link #startPendingIntentMaybeDismissingKeyguard(PendingIntent, Runnable, * ActivityTransitionAnimator.Controller)}, but also specifies a fill-in intent and extra * option that could be used to populate the pending intent and launch the activity. This also - * allows the caller to avoid dismissing the shade. + * allows the caller to avoid dismissing the shade. An optional custom message can be set as + * the unlock reason in the alternate bouncer. */ void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent, boolean dismissShade, @Nullable Runnable intentSentUiThreadCallback, @Nullable ActivityTransitionAnimator.Controller animationController, @Nullable Intent fillInIntent, - @Nullable Bundle extraOptions); + @Nullable Bundle extraOptions, + @Nullable String customMessage + ); /** * The intent flag can be specified in startActivity(). @@ -134,14 +137,20 @@ public interface ActivityStarter { void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel, boolean afterKeyguardGone); - /** Authenticates if needed and dismisses keyguard to execute an action. */ + /** + * Authenticates if needed and dismisses keyguard to execute an action. + * + * TODO(b/348431835) Display the custom message in the new alternate bouncer, when the + * device_entry_udfps_refactor flag is enabled. + */ void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel, boolean afterKeyguardGone, @Nullable String customMessage); /** Starts an activity and dismisses keyguard. */ void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned, - boolean dismissShade); + boolean dismissShade, + @Nullable String customMessage); /** Starts an activity and dismisses keyguard. */ void startActivityDismissingKeyguard(Intent intent, diff --git a/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml b/packages/SystemUI/res/color/connected_network_primary_color.xml index be063a9631e8..f173c8dd5473 100644 --- a/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml +++ b/packages/SystemUI/res/color/connected_network_primary_color.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2024 The Android Open Source Project ~ @@ -13,14 +12,9 @@ ~ 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. ---> -<com.android.systemui.animation.view.LaunchableImageView - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_height="@dimen/dream_overlay_bottom_affordance_height" - android:layout_width="@dimen/dream_overlay_bottom_affordance_width" - android:layout_gravity="bottom|start" - android:padding="@dimen/dream_overlay_bottom_affordance_padding" - android:scaleType="fitCenter" - android:tint="?android:attr/textColorPrimary" - android:src="@drawable/ic_widgets" - android:contentDescription="@string/accessibility_action_open_communal_hub" /> + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:color="?androidprv:attr/materialColorOnPrimaryContainer" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_widgets.xml b/packages/SystemUI/res/drawable/ic_widgets.xml deleted file mode 100644 index b21d047f9386..000000000000 --- a/packages/SystemUI/res/drawable/ic_widgets.xml +++ /dev/null @@ -1,27 +0,0 @@ -<!-- - ~ Copyright (C) 2024 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<!-- go/gm2-icons, from gs_widgets_vd_theme_24.xml --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:tint="?attr/colorControlNormal" - android:viewportHeight="960" - android:viewportWidth="960"> - <path - android:fillColor="@android:color/black" - android:pathData="M666,520L440,294L666,68L892,294L666,520ZM120,440L120,120L440,120L440,440L120,440ZM520,840L520,520L840,520L840,840L520,840ZM120,840L120,520L440,520L440,840L120,840ZM200,360L360,360L360,200L200,200L200,360ZM667,408L780,295L667,182L554,295L667,408ZM600,760L760,760L760,600L600,600L600,760ZM200,760L360,760L360,600L200,600L200,760ZM360,360L360,360L360,360L360,360L360,360ZM554,295L554,295L554,295L554,295L554,295ZM360,600L360,600L360,600L360,600L360,600ZM600,600L600,600L600,600L600,600L600,600Z" /> -</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml index 250188b892f4..fab2d8db859f 100644 --- a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml +++ b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml @@ -16,10 +16,11 @@ --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:color="?android:attr/colorControlHighlight"> <item> <shape android:shape="rectangle"> - <solid android:color="@color/settingslib_state_on_color"/> + <solid android:color="?androidprv:attr/materialColorPrimaryContainer"/> <corners android:radius="@dimen/settingslib_switch_bar_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_on.xml b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml index 5566ea3f62fc..e316a93c1c3d 100644 --- a/packages/SystemUI/res/drawable/settingslib_thumb_on.xml +++ b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml @@ -15,7 +15,8 @@ limitations under the License. --> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <item android:top="@dimen/settingslib_switch_thumb_margin" android:bottom="@dimen/settingslib_switch_thumb_margin"> @@ -23,7 +24,7 @@ <size android:height="@dimen/settingslib_switch_thumb_size" android:width="@dimen/settingslib_switch_thumb_size"/> - <solid android:color="@color/settingslib_state_on_color"/> + <solid android:color="?androidprv:attr/materialColorOnPrimary"/> </shape> </item> </layer-list> diff --git a/packages/SystemUI/res/drawable/settingslib_track_on_background.xml b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml index 1d9dacd6c0f9..e2e64684f9c3 100644 --- a/packages/SystemUI/res/drawable/settingslib_track_on_background.xml +++ b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml @@ -16,11 +16,12 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle" android:width="@dimen/settingslib_switch_track_width" android:height="@dimen/settingslib_switch_track_height"> <padding android:left="@dimen/settingslib_switch_thumb_margin" android:right="@dimen/settingslib_switch_thumb_margin"/> - <solid android:color="@color/settingslib_track_on_color"/> + <solid android:color="?androidprv:attr/materialColorPrimary"/> <corners android:radius="@dimen/settingslib_switch_track_radius"/> </shape> diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index d377e0196e98..21f1cfbe4c9e 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -104,10 +104,6 @@ <color name="people_tile_background">@color/material_dynamic_secondary20</color> - <!-- Internet Dialog --> - <color name="connected_network_primary_color">@color/material_dynamic_primary80</color> - <color name="connected_network_secondary_color">@color/material_dynamic_secondary80</color> - <!-- Keyboard shortcut helper dialog --> <color name="ksh_key_item_color">@*android:color/system_on_surface_variant_dark</color> </resources> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 546bf1c3ac95..e9dd039f38c2 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -60,18 +60,6 @@ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> </style> - <style name="TextAppearance.InternetDialog.Active"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textSize">16sp</item> - <item name="android:textColor">@color/material_dynamic_primary80</item> - <item name="android:textDirection">locale</item> - </style> - - <style name="TextAppearance.InternetDialog.Secondary.Active"> - <item name="android:textSize">14sp</item> - <item name="android:textColor">@color/material_dynamic_secondary80</item> - </style> - <style name="ShortcutHelperTheme" parent="@style/ShortcutHelperThemeCommon"> <item name="android:windowLightNavigationBar">false</item> </style> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index b3d3021028e1..0350cd7dab98 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -236,11 +236,8 @@ <!-- Internet Dialog --> <!-- Material next state on color--> <color name="settingslib_state_on_color">@color/settingslib_state_on</color> - <!-- Material next track on color--> - <color name="settingslib_track_on_color">@color/settingslib_track_on</color> <!-- Material next track off color--> <color name="settingslib_track_off_color">@color/settingslib_track_off</color> - <color name="connected_network_primary_color">#191C18</color> <color name="connected_network_secondary_color">#41493D</color> <color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2f61b12949e5..e92b942635f2 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1193,6 +1193,8 @@ <string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string> <!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] --> <string name="button_to_configure_widgets_text">Customize widgets</string> + <!-- Text for unlock reason on the bouncer before customizing widgets. [CHAR LIMIT=NONE] --> + <string name="unlock_reason_to_customize_widgets">Unlock to customize widgets</string> <!-- Description for the App icon of disabled widget. [CHAR LIMIT=NONE] --> <string name="icon_description_for_disabled_widget">App icon for disabled widget</string> <!-- Description for the App icon of a package that is currently being installed. [CHAR LIMIT=NONE] --> @@ -1393,7 +1395,7 @@ <!-- Text which is shown in the expanded notification shade when there are currently no notifications visible that the user hasn't already seen. [CHAR LIMIT=30] --> <string name="no_unseen_notif_text">No new notifications</string> - <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=30] --> + <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=50] --> <string name="adaptive_notification_edu_hun_title">Adaptive notifications is on</string> <!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 73b7586f1210..7475eb2eceaa 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -1315,7 +1315,7 @@ <item name="android:background">?android:attr/selectableItemBackground</item> </style> - <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault"> + <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault.DayNight"> <item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item> </style> @@ -1358,6 +1358,7 @@ <style name="InternetDialog.NetworkTitle.Active"> <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Active</item> + <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item> </style> <style name="InternetDialog.NetworkSummary"> @@ -1370,18 +1371,19 @@ <style name="InternetDialog.NetworkSummary.Active"> <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary.Active </item> + <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item> </style> <style name="TextAppearance.InternetDialog"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textSize">16sp</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> <item name="android:textDirection">locale</item> </style> <style name="TextAppearance.InternetDialog.Secondary"> <item name="android:textSize">14sp</item> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item> </style> <style name="TextAppearance.InternetDialog.Active"/> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index d5bc10a322d4..c00007b55482 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -22,7 +22,7 @@ import android.os.Bundle; import android.view.MotionEvent; import com.android.systemui.shared.recents.ISystemUiProxy; -// Next ID: 29 +// Next ID: 34 oneway interface IOverviewProxy { void onActiveNavBarRegionChanges(in Region activeRegion) = 11; @@ -83,6 +83,11 @@ oneway interface IOverviewProxy { void onSystemBarAttributesChanged(int displayId, int behavior) = 20; /** + * Sent when {@link TaskbarDelegate#onTransitionModeUpdated} is called. + */ + void onTransitionModeUpdated(int barMode, boolean checkBarModes) = 21; + + /** * Sent when the desired dark intensity of the nav buttons has changed */ void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22; @@ -101,4 +106,30 @@ oneway interface IOverviewProxy { * Sent when the task bar stash state is toggled. */ void onTaskbarToggled() = 27; + + /** + * Sent when the wallpaper visibility is updated. + */ + void updateWallpaperVisibility(int displayId, boolean visible) = 29; + + /** + * Sent when {@link TaskbarDelegate#checkNavBarModes} is called. + */ + void checkNavBarModes() = 30; + + /** + * Sent when {@link TaskbarDelegate#finishBarAnimations} is called. + */ + void finishBarAnimations() = 31; + + /** + * Sent when {@link TaskbarDelegate#touchAutoDim} is called. {@param reset} is true, when auto + * dim is reset after a timeout. + */ + void touchAutoDim(boolean reset) = 32; + + /** + * Sent when {@link TaskbarDelegate#transitionTo} is called. + */ + void transitionTo(int barMode, boolean animate) = 33; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/shared/src/com/android/systemui/shared/statusbar/phone/BarTransitions.java index f62a79f199e9..c123306880ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/statusbar/phone/BarTransitions.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,13 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone; +package com.android.systemui.shared.statusbar.phone; +import android.annotation.ColorInt; import android.annotation.IntDef; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; @@ -34,8 +36,6 @@ import android.util.Log; import android.view.View; import com.android.app.animation.Interpolators; -import com.android.settingslib.Utils; -import com.android.systemui.res.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -44,6 +44,11 @@ public class BarTransitions { private static final boolean DEBUG = false; private static final boolean DEBUG_COLORS = false; + @ColorInt + private static final int SYSTEM_BAR_BACKGROUND_OPAQUE = Color.BLACK; + @ColorInt + private static final int SYSTEM_BAR_BACKGROUND_TRANSPARENT = Color.TRANSPARENT; + public static final int MODE_TRANSPARENT = 0; public static final int MODE_SEMI_TRANSPARENT = 1; public static final int MODE_TRANSLUCENT = 2; @@ -183,11 +188,11 @@ public class BarTransitions { mTransparent = 0x2f0000ff; mWarning = 0xffff0000; } else { - mOpaque = context.getColor(R.color.system_bar_background_opaque); + mOpaque = SYSTEM_BAR_BACKGROUND_OPAQUE; mSemiTransparent = context.getColor( com.android.internal.R.color.system_bar_background_semi_transparent); - mTransparent = context.getColor(R.color.system_bar_background_transparent); - mWarning = Utils.getColorAttrDefaultColor(context, android.R.attr.colorError); + mTransparent = SYSTEM_BAR_BACKGROUND_TRANSPARENT; + mWarning = getColorAttrDefaultColor(context, android.R.attr.colorError, 0); } mGradient = context.getDrawable(gradientResourceId); } @@ -226,7 +231,7 @@ public class BarTransitions { @Override public void setTint(int color) { PorterDuff.Mode targetMode = mTintFilter == null ? Mode.SRC_IN : - mTintFilter.getMode(); + mTintFilter.getMode(); if (mTintFilter == null || mTintFilter.getColor() != color) { mTintFilter = new PorterDuffColorFilter(color, targetMode); } @@ -304,10 +309,13 @@ public class BarTransitions { Interpolators.LINEAR.getInterpolation(t), 1)); mGradientAlpha = (int)(v * targetGradientAlpha + mGradientAlphaStart * (1 - v)); mColor = Color.argb( - (int)(v * Color.alpha(targetColor) + Color.alpha(mColorStart) * (1 - v)), - (int)(v * Color.red(targetColor) + Color.red(mColorStart) * (1 - v)), - (int)(v * Color.green(targetColor) + Color.green(mColorStart) * (1 - v)), - (int)(v * Color.blue(targetColor) + Color.blue(mColorStart) * (1 - v))); + (int) (v * Color.alpha(targetColor) + Color.alpha(mColorStart) * (1 + - v)), + (int) (v * Color.red(targetColor) + Color.red(mColorStart) * (1 - v)), + (int) (v * Color.green(targetColor) + Color.green(mColorStart) * (1 + - v)), + (int) (v * Color.blue(targetColor) + Color.blue(mColorStart) * (1 + - v))); } } if (mGradientAlpha > 0) { @@ -332,4 +340,13 @@ public class BarTransitions { } } } + + /** Get color styled attribute {@code attr}, default to {@code defValue} if not found. */ + @ColorInt + public static int getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue) { + TypedArray ta = context.obtainStyledAttributes(new int[] {attr}); + @ColorInt int colorAccent = ta.getColor(0, defValue); + ta.recycle(); + return colorAccent; + } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index c0c8b755108c..a614fc118636 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -124,6 +124,8 @@ public class QuickStepContract { public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32; // Touchpad gestures are disabled public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33; + // PiP animation is running + public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34; // Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and // SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants @@ -173,6 +175,7 @@ public class QuickStepContract { SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY, SYSUI_STATE_SHORTCUT_HELPER_SHOWING, SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED, + SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, }) public @interface SystemUiStateFlags {} @@ -277,6 +280,9 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) { str.add("touchpad_gestures_disabled"); } + if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) { + str.add("disable_gesture_pip_animating"); + } return str.toString(); } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 4b0740288844..56de5a3ed88a 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -38,7 +38,7 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dump.DumpManager; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index 9d573d3919b9..4a28d8b05661 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -469,6 +469,7 @@ class MenuViewLayer extends FrameLayout implements private void onSpringAnimationsEndAction() { if (mShouldShowDockTooltip) { + mEduTooltipView.ifPresent(this::removeTooltip); mEduTooltipView = Optional.of(new MenuEduTooltipView(mContext, mMenuViewAppearance)); mEduTooltipView.ifPresent(view -> addTooltipView(view, getContext().getText(R.string.accessibility_floating_button_docking_tooltip), diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java index f905241addeb..636bc5b912e5 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java @@ -38,6 +38,7 @@ import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule; import com.android.systemui.ambient.touch.scrim.ScrimController; import com.android.systemui.ambient.touch.scrim.ScrimManager; import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -103,6 +104,8 @@ public class BouncerSwipeTouchHandler implements TouchHandler { private final UiEventLogger mUiEventLogger; + private final ActivityStarter mActivityStarter; + private final ScrimManager.Callback mScrimManagerCallback = new ScrimManager.Callback() { @Override public void onScrimControllerChanged(ScrimController controller) { @@ -149,11 +152,16 @@ public class BouncerSwipeTouchHandler implements TouchHandler { return true; } - // If scrolling up and keyguard is not locked, dismiss the dream since there's - // no bouncer to show. + // If scrolling up and keyguard is not locked, dismiss both keyguard and the + // dream since there's no bouncer to show. if (e1.getY() > e2.getY() && !mLockPatternUtils.isSecure(mUserTracker.getUserId())) { - mCentralSurfaces.get().awakenDreams(); + mActivityStarter.executeRunnableDismissingKeyguard( + () -> mCentralSurfaces.get().awakenDreams(), + /* cancelAction= */ null, + /* dismissShade= */ true, + /* afterKeyguardGone= */ true, + /* deferred= */ false); return true; } @@ -162,7 +170,6 @@ public class BouncerSwipeTouchHandler implements TouchHandler { // bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer // is fully hidden at full expansion (1) and fully visible when fully collapsed // (0). - final float dragDownAmount = e2.getY() - e1.getY(); final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY()) / mTouchSession.getBounds().height(); setPanelExpansion(1 - screenTravelPercentage); @@ -216,7 +223,8 @@ public class BouncerSwipeTouchHandler implements TouchHandler { FlingAnimationUtils flingAnimationUtilsClosing, @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage, @Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + ActivityStarter activityStarter) { mCentralSurfaces = centralSurfaces; mScrimManager = scrimManager; mNotificationShadeWindowController = notificationShadeWindowController; @@ -229,6 +237,7 @@ public class BouncerSwipeTouchHandler implements TouchHandler { mValueAnimatorCreator = valueAnimatorCreator; mVelocityTrackerFactory = velocityTrackerFactory; mUiEventLogger = uiEventLogger; + mActivityStarter = activityStarter; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 01cc33c6c1f6..e03d160adc8d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -45,7 +45,6 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan import androidx.annotation.LayoutRes import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.Flags.udfpsViewPerformance import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams @@ -82,67 +81,66 @@ import kotlinx.coroutines.launch private const val TAG = "UdfpsControllerOverlay" -@VisibleForTesting -const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" +@VisibleForTesting const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" /** * Keeps track of the overlay state and UI resources associated with a single FingerprintService - * request. This state can persist across configuration changes via the [show] and [hide] - * methods. + * request. This state can persist across configuration changes via the [show] and [hide] methods. */ @ExperimentalCoroutinesApi @UiThread -class UdfpsControllerOverlay @JvmOverloads constructor( - private val context: Context, - private val inflater: LayoutInflater, - private val windowManager: WindowManager, - private val accessibilityManager: AccessibilityManager, - private val statusBarStateController: StatusBarStateController, - private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val dialogManager: SystemUIDialogManager, - private val dumpManager: DumpManager, - private val transitionController: LockscreenShadeTransitionController, - private val configurationController: ConfigurationController, - private val keyguardStateController: KeyguardStateController, - private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, - private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, - val requestId: Long, - @RequestReason val requestReason: Int, - private val controllerCallback: IUdfpsOverlayControllerCallback, - private val onTouch: (View, MotionEvent, Boolean) -> Boolean, - private val activityTransitionAnimator: ActivityTransitionAnimator, - private val primaryBouncerInteractor: PrimaryBouncerInteractor, - private val alternateBouncerInteractor: AlternateBouncerInteractor, - private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, - private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, - private val transitionInteractor: KeyguardTransitionInteractor, - private val selectedUserInteractor: SelectedUserInteractor, - private val deviceEntryUdfpsTouchOverlayViewModel: - Lazy<DeviceEntryUdfpsTouchOverlayViewModel>, - private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>, - private val shadeInteractor: ShadeInteractor, - private val udfpsOverlayInteractor: UdfpsOverlayInteractor, - private val powerInteractor: PowerInteractor, - @Application private val scope: CoroutineScope, +class UdfpsControllerOverlay +@JvmOverloads +constructor( + private val context: Context, + private val inflater: LayoutInflater, + private val windowManager: WindowManager, + private val accessibilityManager: AccessibilityManager, + private val statusBarStateController: StatusBarStateController, + private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val dialogManager: SystemUIDialogManager, + private val dumpManager: DumpManager, + private val transitionController: LockscreenShadeTransitionController, + private val configurationController: ConfigurationController, + private val keyguardStateController: KeyguardStateController, + private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, + private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, + val requestId: Long, + @RequestReason val requestReason: Int, + private val controllerCallback: IUdfpsOverlayControllerCallback, + private val onTouch: (View, MotionEvent, Boolean) -> Boolean, + private val activityTransitionAnimator: ActivityTransitionAnimator, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, + private val alternateBouncerInteractor: AlternateBouncerInteractor, + private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, + private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, + private val transitionInteractor: KeyguardTransitionInteractor, + private val selectedUserInteractor: SelectedUserInteractor, + private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>, + private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>, + private val shadeInteractor: ShadeInteractor, + private val udfpsOverlayInteractor: UdfpsOverlayInteractor, + private val powerInteractor: PowerInteractor, + @Application private val scope: CoroutineScope, ) { private val currentStateUpdatedToOffAodOrDozing: Flow<Unit> = transitionInteractor.currentKeyguardState .filter { - it == KeyguardState.OFF || - it == KeyguardState.AOD || - it == KeyguardState.DOZING + it == KeyguardState.OFF || it == KeyguardState.AOD || it == KeyguardState.DOZING } - .map { } // map to Unit + .map {} // map to Unit private var listenForCurrentKeyguardState: Job? = null private var addViewRunnable: Runnable? = null private var overlayViewLegacy: UdfpsView? = null private set + private var overlayTouchView: UdfpsTouchOverlay? = null /** - * Get the current UDFPS overlay touch view which is a different View depending on whether - * the DeviceEntryUdfpsRefactor flag is enabled or not. + * Get the current UDFPS overlay touch view which is a different View depending on whether the + * DeviceEntryUdfpsRefactor flag is enabled or not. + * * @return The view, when [isShowing], else null */ fun getTouchOverlay(): View? { @@ -158,23 +156,28 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private var overlayTouchListener: TouchExplorationStateChangeListener? = null - private val coreLayoutParams = WindowManager.LayoutParams( - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - 0 /* flags set in computeLayoutParams() */, - PixelFormat.TRANSLUCENT - ).apply { - title = TAG - fitInsetsTypes = 0 - gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT - layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS - flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or - WindowManager.LayoutParams.FLAG_SPLIT_TOUCH) - privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or - WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION - // Avoid announcing window title. - accessibilityTitle = " " - inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY - } + private val coreLayoutParams = + WindowManager.LayoutParams( + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + 0 /* flags set in computeLayoutParams() */, + PixelFormat.TRANSLUCENT + ) + .apply { + title = TAG + fitInsetsTypes = 0 + gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT + layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + flags = + (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or + WindowManager.LayoutParams.FLAG_SPLIT_TOUCH) + privateFlags = + WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or + WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION + // Avoid announcing window title. + accessibilityTitle = " " + inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY + } /** If the overlay is currently showing. */ val isShowing: Boolean @@ -209,51 +212,51 @@ class UdfpsControllerOverlay @JvmOverloads constructor( sensorBounds = Rect(params.sensorBounds) try { if (DeviceEntryUdfpsRefactor.isEnabled) { - overlayTouchView = (inflater.inflate( - R.layout.udfps_touch_overlay, null, false - ) as UdfpsTouchOverlay).apply { - // This view overlaps the sensor area - // prevent it from being selectable during a11y - if (requestReason.isImportantForAccessibility()) { - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - } - - addViewNowOrLater(this, null) - when (requestReason) { - REASON_AUTH_KEYGUARD -> - UdfpsTouchOverlayBinder.bind( - view = this, - viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(), - udfpsOverlayInteractor = udfpsOverlayInteractor, - ) - else -> - UdfpsTouchOverlayBinder.bind( - view = this, - viewModel = defaultUdfpsTouchOverlayViewModel.get(), - udfpsOverlayInteractor = udfpsOverlayInteractor, - ) - } - } + overlayTouchView = + (inflater.inflate(R.layout.udfps_touch_overlay, null, false) + as UdfpsTouchOverlay) + .apply { + // This view overlaps the sensor area + // prevent it from being selectable during a11y + if (requestReason.isImportantForAccessibility()) { + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + } + + addViewNowOrLater(this, null) + when (requestReason) { + REASON_AUTH_KEYGUARD -> + UdfpsTouchOverlayBinder.bind( + view = this, + viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(), + udfpsOverlayInteractor = udfpsOverlayInteractor, + ) + else -> + UdfpsTouchOverlayBinder.bind( + view = this, + viewModel = defaultUdfpsTouchOverlayViewModel.get(), + udfpsOverlayInteractor = udfpsOverlayInteractor, + ) + } + } } else { - overlayViewLegacy = (inflater.inflate( - R.layout.udfps_view, null, false - ) as UdfpsView).apply { - overlayParams = params - setUdfpsDisplayModeProvider(udfpsDisplayModeProvider) - val animation = inflateUdfpsAnimation(this, controller) - if (animation != null) { - animation.init() - animationViewController = animation + overlayViewLegacy = + (inflater.inflate(R.layout.udfps_view, null, false) as UdfpsView).apply { + overlayParams = params + setUdfpsDisplayModeProvider(udfpsDisplayModeProvider) + val animation = inflateUdfpsAnimation(this, controller) + if (animation != null) { + animation.init() + animationViewController = animation + } + // This view overlaps the sensor area + // prevent it from being selectable during a11y + if (requestReason.isImportantForAccessibility()) { + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + } + + addViewNowOrLater(this, animation) + sensorRect = sensorBounds } - // This view overlaps the sensor area - // prevent it from being selectable during a11y - if (requestReason.isImportantForAccessibility()) { - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - } - - addViewNowOrLater(this, animation) - sensorRect = sensorBounds - } } getTouchOverlay()?.apply { touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled @@ -269,7 +272,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } } accessibilityManager.addTouchExplorationStateChangeListener( - overlayTouchListener!! + overlayTouchListener!! ) overlayTouchListener?.onTouchExplorationStateChanged(true) } @@ -284,30 +287,18 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } private fun addViewNowOrLater(view: View, animation: UdfpsAnimationViewController<*>?) { - if (udfpsViewPerformance()) { - addViewRunnable = kotlinx.coroutines.Runnable { + addViewRunnable = + kotlinx.coroutines.Runnable { Trace.setCounter("UdfpsAddView", 1) - windowManager.addView( - view, - coreLayoutParams.updateDimensions(animation) - ) - } - if (powerInteractor.detailedWakefulness.value.isAwake()) { - // Device is awake, so we add the view immediately. - addViewIfPending() - } else { - listenForCurrentKeyguardState?.cancel() - listenForCurrentKeyguardState = scope.launch { - currentStateUpdatedToOffAodOrDozing.collect { - addViewIfPending() - } - } + windowManager.addView(view, coreLayoutParams.updateDimensions(animation)) } + if (powerInteractor.detailedWakefulness.value.isAwake()) { + // Device is awake, so we add the view immediately. + addViewIfPending() } else { - windowManager.addView( - view, - coreLayoutParams.updateDimensions(animation) - ) + listenForCurrentKeyguardState?.cancel() + listenForCurrentKeyguardState = + scope.launch { currentStateUpdatedToOffAodOrDozing.collect { addViewIfPending() } } } } @@ -340,23 +331,26 @@ class UdfpsControllerOverlay @JvmOverloads constructor( ): UdfpsAnimationViewController<*>? { DeviceEntryUdfpsRefactor.assertInLegacyMode() - val isEnrollment = when (requestReason) { - REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true - else -> false - } + val isEnrollment = + when (requestReason) { + REASON_ENROLL_FIND_SENSOR, + REASON_ENROLL_ENROLLING -> true + else -> false + } - val filteredRequestReason = if (isEnrollment && shouldRemoveEnrollmentUi()) { - REASON_AUTH_OTHER - } else { - requestReason - } + val filteredRequestReason = + if (isEnrollment && shouldRemoveEnrollmentUi()) { + REASON_AUTH_OTHER + } else { + requestReason + } return when (filteredRequestReason) { REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> { // Enroll udfps UI is handled by settings, so use empty view here UdfpsFpmEmptyViewController( - view.addUdfpsView(R.layout.udfps_fpm_empty_view){ + view.addUdfpsView(R.layout.udfps_fpm_empty_view) { updateAccessibilityViewLocation(sensorBounds) }, statusBarStateController, @@ -434,14 +428,10 @@ class UdfpsControllerOverlay @JvmOverloads constructor( udfpsDisplayModeProvider.disable(null) } getTouchOverlay()?.apply { - if (udfpsViewPerformance()) { - if (this.parent != null) { - windowManager.removeView(this) - } - Trace.setCounter("UdfpsAddView", 0) - } else { + if (this.parent != null) { windowManager.removeView(this) } + Trace.setCounter("UdfpsAddView", 0) setOnTouchListener(null) setOnHoverListener(null) overlayTouchListener?.let { @@ -475,22 +465,19 @@ class UdfpsControllerOverlay @JvmOverloads constructor( val paddingX = animation?.paddingX ?: 0 val paddingY = animation?.paddingY ?: 0 - val isEnrollment = when (requestReason) { - REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true - else -> false - } + val isEnrollment = + when (requestReason) { + REASON_ENROLL_FIND_SENSOR, + REASON_ENROLL_ENROLLING -> true + else -> false + } // Use expanded overlay unless touchExploration enabled var rotatedBounds = if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) { Rect(overlayParams.sensorBounds) } else { - Rect( - 0, - 0, - overlayParams.naturalDisplayWidth, - overlayParams.naturalDisplayHeight - ) + Rect(0, 0, overlayParams.naturalDisplayWidth, overlayParams.naturalDisplayHeight) } val rot = overlayParams.rotation @@ -498,7 +485,8 @@ class UdfpsControllerOverlay @JvmOverloads constructor( if (!shouldRotate(animation)) { Log.v( TAG, - "Skip rotating UDFPS bounds " + Surface.rotationToString(rot) + + "Skip rotating UDFPS bounds " + + Surface.rotationToString(rot) + " animation=$animation" + " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" + " isOccluded=${keyguardStateController.isOccluded}" @@ -559,6 +547,4 @@ class UdfpsControllerOverlay @JvmOverloads constructor( @RequestReason private fun Int.isImportantForAccessibility() = - this == REASON_ENROLL_FIND_SENSOR || - this == REASON_ENROLL_ENROLLING || - this == REASON_AUTH_BP + this == REASON_ENROLL_FIND_SENSOR || this == REASON_ENROLL_ENROLLING || this == REASON_AUTH_BP diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt index 94f465d3c1c3..eaddc42dcd5a 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt @@ -288,11 +288,13 @@ constructor( private fun startSettingsActivity(intent: Intent, view: View) { if (job?.isActive == true) { intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP - activityStarter.postStartActivityDismissingKeyguard( - intent, - 0, - dialogTransitionAnimator.createActivityTransitionController(view) - ) + val controller = dialogTransitionAnimator.createActivityTransitionController(view) + // The controller will be null when the screen is locked and going to show the + // primary bouncer. In this case we dismiss the dialog manually. + if (controller == null) { + cancelJob() + } + activityStarter.postStartActivityDismissingKeyguard(intent, 0, controller) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt index 5cd15f278f00..75f0badfc7cb 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt @@ -17,7 +17,6 @@ package com.android.systemui.communal.data.model import android.appwidget.AppWidgetProviderInfo -import com.android.settingslib.flags.Flags.allowAllWidgetsOnLockscreenByDefault /** * The widget categories to display on communal hub (where categories is a bitfield with values that @@ -31,9 +30,7 @@ value class CommunalWidgetCategories(val categories: Int = defaultCategories) { val defaultCategories: Int get() { return AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or - if (allowAllWidgetsOnLockscreenByDefault()) - AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN - else 0 + AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt index 1c47e507c972..2940a95fdc33 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt @@ -24,7 +24,6 @@ import android.provider.Settings import com.android.systemui.Flags.communalHub import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.communal.data.model.CommunalEnabledState -import com.android.systemui.communal.data.model.CommunalWidgetCategories import com.android.systemui.communal.data.model.DisabledReason import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_DEVICE_POLICY import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG @@ -52,12 +51,6 @@ interface CommunalSettingsRepository { /** A [CommunalEnabledState] for the specified user. */ fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> - /** - * A flow that reports the widget categories to show on the hub as selected by the user in - * Settings. - */ - fun getWidgetCategories(user: UserInfo): Flow<CommunalWidgetCategories> - /** Keyguard widgets enabled state by Device Policy Manager for the specified user. */ fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> @@ -104,22 +97,6 @@ constructor( .flowOn(bgDispatcher) } - override fun getWidgetCategories(user: UserInfo): Flow<CommunalWidgetCategories> = - secureSettings - .observerFlow(userId = user.id, names = arrayOf(GLANCEABLE_HUB_CONTENT_SETTING)) - // Force an update - .onStart { emit(Unit) } - .map { - CommunalWidgetCategories( - secureSettings.getIntForUser( - GLANCEABLE_HUB_CONTENT_SETTING, - CommunalWidgetCategories.defaultCategories, - user.id - ) - ) - } - .flowOn(bgDispatcher) - override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> = broadcastDispatcher .broadcastFlow( @@ -159,7 +136,6 @@ constructor( } companion object { - const val GLANCEABLE_HUB_CONTENT_SETTING = "glanceable_hub_content_setting" const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background" private const val ENABLED_SETTING_DEFAULT = 1 } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 9f3ade9cd425..f5255ac4d545 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -392,26 +392,17 @@ constructor( allowedForWorkProfile -> filterWidgetsAllowedByDevicePolicy(widgets, allowedForWorkProfile) }, - communalSettingsInteractor.communalWidgetCategories, updateOnWorkProfileBroadcastReceived, - ) { widgets, allowedCategories, _ -> + ) { widgets, _ -> widgets.map { widget -> when (widget) { is CommunalWidgetContentModel.Available -> { - if (widget.providerInfo.widgetCategory and allowedCategories != 0) { - // At least one category this widget specified is allowed, so show it - WidgetContent.Widget( - appWidgetId = widget.appWidgetId, - providerInfo = widget.providerInfo, - appWidgetHost = appWidgetHost, - inQuietMode = isQuietModeEnabled(widget.providerInfo.profile) - ) - } else { - WidgetContent.DisabledWidget( - appWidgetId = widget.appWidgetId, - providerInfo = widget.providerInfo, - ) - } + WidgetContent.Widget( + appWidgetId = widget.appWidgetId, + providerInfo = widget.providerInfo, + appWidgetHost = appWidgetHost, + inQuietMode = isQuietModeEnabled(widget.providerInfo.profile) + ) } is CommunalWidgetContentModel.Pending -> { WidgetContent.PendingWidget( diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt index f043d58543fc..47b75c458d20 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -19,7 +19,6 @@ package com.android.systemui.communal.domain.interactor import android.content.pm.UserInfo import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.communal.data.model.CommunalEnabledState -import com.android.systemui.communal.data.model.CommunalWidgetCategories import com.android.systemui.communal.data.repository.CommunalSettingsRepository import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.dagger.SysUISingleton @@ -70,18 +69,6 @@ constructor( // Start this eagerly since the value is accessed synchronously in many places. .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false) - /** What widget categories to show on the hub. */ - val communalWidgetCategories: StateFlow<Int> = - userInteractor.selectedUserInfo - .flatMapLatest { user -> repository.getWidgetCategories(user) } - .map { categories -> categories.categories } - .stateIn( - scope = bgScope, - // Start this eagerly since the value can be accessed synchronously. - started = SharingStarted.Eagerly, - initialValue = CommunalWidgetCategories.defaultCategories - ) - /** The type of background to use for the hub. Used to experiment with different backgrounds */ val communalBackground: Flow<CommunalBackgroundType> = userInteractor.selectedUserInfo diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index 9185384e79a3..fab243575670 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -25,6 +25,7 @@ import android.util.Log import androidx.activity.result.ActivityResultLauncher import com.android.internal.logging.UiEventLogger import com.android.systemui.Flags.enableWidgetPickerSizeFilter +import com.android.systemui.communal.data.model.CommunalWidgetCategories import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -183,7 +184,7 @@ constructor( } putExtra( AppWidgetManager.EXTRA_CATEGORY_FILTER, - communalSettingsInteractor.communalWidgetCategories.value + CommunalWidgetCategories.defaultCategories ) putExtra(EXTRA_UI_SURFACE_KEY, EXTRA_UI_SURFACE_VALUE) putParcelableArrayListExtra(EXTRA_ADDED_APP_WIDGETS_KEY, excludeList) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt index 2043dd1c557c..0466bbc748d9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -27,6 +27,7 @@ import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -55,6 +56,8 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -64,6 +67,7 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** The default view model used for showing the communal hub. */ @@ -74,6 +78,7 @@ class CommunalViewModel constructor( @Main val mainDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, + @Background private val bgScope: CoroutineScope, @Main private val resources: Resources, keyguardTransitionInteractor: KeyguardTransitionInteractor, keyguardInteractor: KeyguardInteractor, @@ -303,8 +308,12 @@ constructor( * * This is needed because the notification shade does not block touches in blank areas and these * fall through to the glanceable hub, which we don't want. + * + * Using a StateFlow as the value does not necessarily change when hub becomes available. */ - val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded) + val touchesAllowed: StateFlow<Boolean> = + not(shadeInteractor.isAnyFullyExpanded) + .stateIn(bgScope, SharingStarted.Eagerly, initialValue = false) // TODO(b/339667383): remove this temporary swipe gesture handle /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt index 76be0055c44b..af87f09d3c89 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt @@ -22,6 +22,7 @@ import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import javax.inject.Inject interface EditWidgetsActivityStarter { @@ -48,6 +49,7 @@ constructor( }, /* onlyProvisioned = */ true, /* dismissShade = */ true, + applicationContext.resources.getString(R.string.unlock_reason_to_customize_widgets), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt index cbc6c977e3e6..72f9180c51d2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt @@ -34,10 +34,11 @@ constructor( private val activityStarter: ActivityStarter, ) : RemoteViews.InteractionHandler { - private val delegate = InteractionHandlerDelegate( - findViewToAnimate = { view -> view is CommunalAppWidgetHostView }, - intentStarter = this::startIntent, - ) + private val delegate = + InteractionHandlerDelegate( + findViewToAnimate = { view -> view is CommunalAppWidgetHostView }, + intentStarter = this::startIntent, + ) override fun onInteraction( view: View, @@ -45,7 +46,6 @@ constructor( response: RemoteViews.RemoteResponse ): Boolean = delegate.onInteraction(view, pendingIntent, response) - private fun startIntent( pendingIntent: PendingIntent, fillInIntent: Intent, @@ -59,6 +59,8 @@ constructor( controller, fillInIntent, extraOptions.toBundle(), + // TODO(b/325110448): UX to provide copy + /* customMessage = */ null, ) return true } diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java index 92108e9e7103..afa23755d937 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java @@ -18,7 +18,6 @@ package com.android.systemui.complication; import static com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW; import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS; -import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE; @@ -90,7 +89,6 @@ public class DreamHomeControlsComplication implements Complication { private final DreamHomeControlsComplication mComplication; private final DreamOverlayStateController mDreamOverlayStateController; private final ControlsComponent mControlsComponent; - private final boolean mReplacedByOpenHub; private boolean mOverlayActive = false; @@ -118,13 +116,11 @@ public class DreamHomeControlsComplication implements Complication { public Registrant(DreamHomeControlsComplication complication, DreamOverlayStateController dreamOverlayStateController, ControlsComponent controlsComponent, - @SystemUser Monitor monitor, - @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replacedByOpenHub) { + @SystemUser Monitor monitor) { super(monitor); mComplication = complication; mControlsComponent = controlsComponent; mDreamOverlayStateController = dreamOverlayStateController; - mReplacedByOpenHub = replacedByOpenHub; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java deleted file mode 100644 index 05df2bb4aa59..000000000000 --- a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.complication; - -import static com.android.systemui.complication.dagger.OpenHubComplicationComponent.OpenHubModule.OPEN_HUB_CHIP_VIEW; -import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.OPEN_HUB_CHIP_LAYOUT_PARAMS; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.util.Log; -import android.view.View; -import android.widget.ImageView; - -import com.android.settingslib.Utils; -import com.android.systemui.CoreStartable; -import com.android.systemui.communal.domain.interactor.CommunalInteractor; -import com.android.systemui.communal.shared.model.CommunalScenes; -import com.android.systemui.complication.dagger.OpenHubComplicationComponent; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dagger.qualifiers.SystemUser; -import com.android.systemui.dreams.DreamOverlayStateController; -import com.android.systemui.shared.condition.Monitor; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.util.ViewController; -import com.android.systemui.util.condition.ConditionalCoreStartable; - -import javax.inject.Inject; -import javax.inject.Named; - -/** - * A dream complication that shows a chip to open the glanceable hub. - */ -// TODO(b/339667383): delete or properly implement this once a product decision is made -public class OpenHubComplication implements Complication { - private final Resources mResources; - private final OpenHubComplicationComponent.Factory mComponentFactory; - - @Inject - public OpenHubComplication( - @Main Resources resources, - OpenHubComplicationComponent.Factory componentFactory) { - mResources = resources; - mComponentFactory = componentFactory; - } - - @Override - public ViewHolder createView(ComplicationViewModel model) { - return mComponentFactory.create(mResources).getViewHolder(); - } - - @Override - public int getRequiredTypeAvailability() { - // TODO(b/339667383): create a new complication type if we decide to productionize this - return COMPLICATION_TYPE_NONE; - } - - /** - * {@link CoreStartable} for registering the complication with SystemUI on startup. - */ - public static class Registrant extends ConditionalCoreStartable { - private final OpenHubComplication mComplication; - private final DreamOverlayStateController mDreamOverlayStateController; - - private boolean mOverlayActive = false; - - private final DreamOverlayStateController.Callback mOverlayStateCallback = - new DreamOverlayStateController.Callback() { - @Override - public void onStateChanged() { - if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) { - return; - } - - mOverlayActive = !mOverlayActive; - - if (mOverlayActive) { - updateOpenHubComplication(); - } - } - }; - - @Inject - public Registrant(OpenHubComplication complication, - DreamOverlayStateController dreamOverlayStateController, - @SystemUser Monitor monitor) { - super(monitor); - mComplication = complication; - mDreamOverlayStateController = dreamOverlayStateController; - } - - @Override - public void onStart() { - mDreamOverlayStateController.addCallback(mOverlayStateCallback); - } - - private void updateOpenHubComplication() { - // TODO(b/339667383): don't show the complication if glanceable hub is disabled -// if (Flags.glanceableHubShortcutButton()) { -// mDreamOverlayStateController.addComplication(mComplication); -// } else { -// mDreamOverlayStateController.removeComplication(mComplication); -// } - } - } - - /** - * Contains values/logic associated with the dream complication view. - */ - public static class OpenHubChipViewHolder implements ViewHolder { - private final ImageView mView; - private final ComplicationLayoutParams mLayoutParams; - private final OpenHubChipViewController mViewController; - - @Inject - OpenHubChipViewHolder( - OpenHubChipViewController dreamOpenHubChipViewController, - @Named(OPEN_HUB_CHIP_VIEW) ImageView view, - @Named(OPEN_HUB_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams - ) { - mView = view; - mLayoutParams = layoutParams; - mViewController = dreamOpenHubChipViewController; - mViewController.init(); - } - - @Override - public ImageView getView() { - return mView; - } - - @Override - public ComplicationLayoutParams getLayoutParams() { - return mLayoutParams; - } - } - - /** - * Controls behavior of the dream complication. - */ - static class OpenHubChipViewController extends ViewController<ImageView> { - private static final String TAG = "OpenHubCtrl"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - - private final Context mContext; - private final ConfigurationController mConfigurationController; - - private final ConfigurationController.ConfigurationListener mConfigurationListener = - new ConfigurationController.ConfigurationListener() { - @Override - public void onUiModeChanged() { - reloadResources(); - } - }; - private final CommunalInteractor mCommunalInteractor; - - @Inject - OpenHubChipViewController( - @Named(OPEN_HUB_CHIP_VIEW) ImageView view, - Context context, - ConfigurationController configurationController, - CommunalInteractor communalInteractor) { - super(view); - - mContext = context; - mConfigurationController = configurationController; - mCommunalInteractor = communalInteractor; - } - - @Override - protected void onViewAttached() { - reloadResources(); - mView.setOnClickListener(this::onClickOpenHub); - mConfigurationController.addCallback(mConfigurationListener); - } - - @Override - protected void onViewDetached() { - mConfigurationController.removeCallback(mConfigurationListener); - } - - private void reloadResources() { - mView.setImageTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); - final Drawable background = mView.getBackground(); - if (background != null) { - background.setTintList( - Utils.getColorAttr(mContext, com.android.internal.R.attr.colorSurface)); - } - } - - private void onClickOpenHub(View v) { - if (DEBUG) Log.d(TAG, "open hub complication tapped"); - - mCommunalInteractor.changeScene(CommunalScenes.Communal, null); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java deleted file mode 100644 index 501601ee32ba..000000000000 --- a/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.complication.dagger; - -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.view.LayoutInflater; -import android.widget.ImageView; - -import com.android.systemui.complication.OpenHubComplication; -import com.android.systemui.res.R; -import com.android.systemui.shared.shadow.DoubleShadowIconDrawable; -import com.android.systemui.shared.shadow.DoubleShadowTextHelper; - -import dagger.BindsInstance; -import dagger.Module; -import dagger.Provides; -import dagger.Subcomponent; - -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; - -import javax.inject.Named; -import javax.inject.Scope; - -/** - * Responsible for generating dependencies for the {@link OpenHubComplication}. - */ -@Subcomponent(modules = OpenHubComplicationComponent.OpenHubModule.class) -@OpenHubComplicationComponent.OpenHubComplicationScope -public interface OpenHubComplicationComponent { - /** - * Creates a view holder for the open hub complication. - */ - OpenHubComplication.OpenHubChipViewHolder getViewHolder(); - - /** - * Scope of the open hub complication. - */ - @Documented - @Retention(RUNTIME) - @Scope - @interface OpenHubComplicationScope { - } - - /** - * Factory that generates a {@link OpenHubComplicationComponent}. - */ - @Subcomponent.Factory - interface Factory { - /** - * Creates an instance of {@link OpenHubComplicationComponent}. - */ - OpenHubComplicationComponent create(@BindsInstance Resources resources); - } - - /** - * Scoped injected values for the {@link OpenHubComplicationComponent}. - */ - @Module - interface OpenHubModule { - String OPEN_HUB_CHIP_VIEW = "open_hub_chip_view"; - String OPEN_HUB_BACKGROUND_DRAWABLE = "open_hub_background_drawable"; - - /** - * Provides the dream open hub chip view. - */ - @Provides - @OpenHubComplicationScope - @Named(OPEN_HUB_CHIP_VIEW) - static ImageView provideOpenHubChipView( - LayoutInflater layoutInflater, - @Named(OPEN_HUB_BACKGROUND_DRAWABLE) Drawable backgroundDrawable) { - final ImageView chip = - (ImageView) layoutInflater.inflate(R.layout.dream_overlay_open_hub_chip, - null, false); - chip.setBackground(backgroundDrawable); - - return chip; - } - - /** - * Provides the background drawable for the open hub chip. - */ - @Provides - @OpenHubComplicationScope - @Named(OPEN_HUB_BACKGROUND_DRAWABLE) - static Drawable providesOpenHubBackground(Context context, Resources resources) { - return new DoubleShadowIconDrawable(createShadowInfo( - resources, - R.dimen.dream_overlay_bottom_affordance_key_text_shadow_radius, - R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dx, - R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dy, - R.dimen.dream_overlay_bottom_affordance_key_shadow_alpha - ), - createShadowInfo( - resources, - R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_radius, - R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dx, - R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dy, - R.dimen.dream_overlay_bottom_affordance_ambient_shadow_alpha - ), - resources.getDrawable(R.drawable.dream_overlay_bottom_affordance_bg), - resources.getDimensionPixelOffset( - R.dimen.dream_overlay_bottom_affordance_width), - resources.getDimensionPixelSize(R.dimen.dream_overlay_bottom_affordance_inset) - ); - } - - private static DoubleShadowTextHelper.ShadowInfo createShadowInfo(Resources resources, - int blurId, int offsetXId, int offsetYId, int alphaId) { - - return new DoubleShadowTextHelper.ShadowInfo( - resources.getDimension(blurId), - resources.getDimension(offsetXId), - resources.getDimension(offsetYId), - resources.getFloat(alphaId) - ); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java index edb5ff7799be..6f1b09829671 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java @@ -25,7 +25,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; -import com.android.systemui.util.settings.SystemSettings; import dagger.Module; import dagger.Provides; @@ -40,7 +39,6 @@ import javax.inject.Named; subcomponents = { DreamClockTimeComplicationComponent.class, DreamHomeControlsComplicationComponent.class, - OpenHubComplicationComponent.class, DreamMediaEntryComplicationComponent.class }) public interface RegisteredComplicationsModule { @@ -48,8 +46,6 @@ public interface RegisteredComplicationsModule { String DREAM_SMARTSPACE_LAYOUT_PARAMS = "smartspace_layout_params"; String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params"; String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params"; - String OPEN_HUB_CHIP_LAYOUT_PARAMS = "open_hub_chip_layout_params"; - String OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS = "open_hub_chip_replace_home_controls"; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT_NO_SMARTSPACE = 2; @@ -113,26 +109,6 @@ public interface RegisteredComplicationsModule { } /** - * Provides layout parameters for the open hub complication. - */ - @Provides - @Named(OPEN_HUB_CHIP_LAYOUT_PARAMS) - static ComplicationLayoutParams provideOpenHubLayoutParams( - @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replaceHomeControls) { - int position = ComplicationLayoutParams.POSITION_BOTTOM | (replaceHomeControls - ? ComplicationLayoutParams.POSITION_START - : ComplicationLayoutParams.POSITION_END); - int direction = replaceHomeControls ? ComplicationLayoutParams.DIRECTION_END - : ComplicationLayoutParams.DIRECTION_START; - return new ComplicationLayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - position, - direction, - DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT); - } - - /** * Provides layout parameters for the smartspace complication. */ @Provides @@ -148,14 +124,4 @@ public interface RegisteredComplicationsModule { res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_padding), res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_max_width)); } - - /** - * If true, the home controls chip should not be shown and the open hub chip should be shown in - * its place. - */ - @Provides - @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) - static boolean providesOpenHubChipReplaceHomeControls(SystemSettings systemSettings) { - return systemSettings.getBool(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS, false); - } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 140434040ca7..af7ecf66d107 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor @@ -62,6 +63,9 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha // CommunalHub dependencies communalHub dependsOn MigrateClocksToBlueprint.token + + // DualShade dependencies + DualShade.token dependsOn SceneContainerFlag.getMainAconfigFlag() } private inline val politeNotifications diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt index 7b5139aa510e..6cbe29ef6f7c 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt @@ -160,7 +160,7 @@ constructor( } fun onTileClick(): Boolean { - if (state == State.TIMEOUT_WAIT) { + if (state == State.TIMEOUT_WAIT || state == State.IDLE) { setState(State.IDLE) qsTile?.let { it.click(expandable) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt index 04bde26fdd88..c00bd6ff93b0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt @@ -16,11 +16,23 @@ package com.android.systemui.keyboard.shortcut.data.repository +import android.view.KeyEvent +import android.view.KeyboardShortcutGroup +import android.view.KeyboardShortcutInfo +import android.view.WindowManager +import android.view.WindowManager.KeyboardShortcutsReceiver import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource +import com.android.systemui.keyboard.shortcut.shared.model.Shortcut import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active +import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory import javax.inject.Inject +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.suspendCancellableCoroutine @SysUISingleton class ShortcutHelperCategoriesRepository @@ -28,11 +40,77 @@ class ShortcutHelperCategoriesRepository constructor( private val systemShortcutsSource: SystemShortcutsSource, private val multitaskingShortcutsSource: MultitaskingShortcutsSource, + private val windowManager: WindowManager, + shortcutHelperStateRepository: ShortcutHelperStateRepository ) { - fun systemShortcutsCategory(): ShortcutCategory = - systemShortcutsSource.systemShortcutsCategory() + val systemShortcutsCategory = + shortcutHelperStateRepository.state.map { + if (it is Active) systemShortcutsSource.systemShortcutsCategory() else null + } - fun multitaskingShortcutsCategory(): ShortcutCategory = - multitaskingShortcutsSource.multitaskingShortcutCategory() + val multitaskingShortcutsCategory = + shortcutHelperStateRepository.state.map { + if (it is Active) multitaskingShortcutsSource.multitaskingShortcutCategory() else null + } + + val imeShortcutsCategory = + shortcutHelperStateRepository.state.map { + if (it is Active) retrieveImeShortcuts(it.deviceId) else null + } + + private suspend fun retrieveImeShortcuts(deviceId: Int): ShortcutCategory { + return suspendCancellableCoroutine { continuation -> + val shortcutsReceiver = KeyboardShortcutsReceiver { shortcutGroups -> + continuation.resumeWith(Result.success(toShortcutCategory(shortcutGroups))) + } + windowManager.requestImeKeyboardShortcuts(shortcutsReceiver, deviceId) + } + } + + private fun toShortcutCategory(shortcutGroups: List<KeyboardShortcutGroup>) = + shortcutCategory(ShortcutCategoryType.IME) { + shortcutGroups.map { shortcutGroup -> + subCategory(shortcutGroup.label.toString(), toShortcuts(shortcutGroup.items)) + } + } + + private fun toShortcuts(infoList: List<KeyboardShortcutInfo>) = + infoList.mapNotNull { toShortcut(it) } + + private fun toShortcut(shortcutInfo: KeyboardShortcutInfo): Shortcut? { + val shortcutCommand = toShortcutCommand(shortcutInfo) + return if (shortcutCommand == null) null + else Shortcut(label = shortcutInfo.label!!.toString(), commands = listOf(shortcutCommand)) + } + + private fun toShortcutCommand(info: KeyboardShortcutInfo): ShortcutCommand? { + val keyCodes = mutableListOf<Int>() + var remainingModifiers = info.modifiers + SUPPORTED_MODIFIERS.forEach { supportedModifier -> + if ((supportedModifier and remainingModifiers) != 0) { + keyCodes += supportedModifier + // "Remove" the modifier from the remaining modifiers + remainingModifiers = remainingModifiers and supportedModifier.inv() + } + } + if (remainingModifiers != 0) { + // There is a remaining modifier we don't support + return null + } + keyCodes += info.keycode + return ShortcutCommand(keyCodes) + } + + companion object { + private val SUPPORTED_MODIFIERS = + listOf( + KeyEvent.META_META_ON, + KeyEvent.META_CTRL_ON, + KeyEvent.META_ALT_ON, + KeyEvent.META_SHIFT_ON, + KeyEvent.META_SYM_ON, + KeyEvent.META_FUNCTION_ON + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt index 883407c5f6a4..57d4b4a02fce 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt @@ -18,30 +18,56 @@ package com.android.systemui.keyboard.shortcut.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository -import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository +import com.android.systemui.keyboard.shortcut.shared.model.Shortcut import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory -import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map @SysUISingleton class ShortcutHelperCategoriesInteractor @Inject constructor( - stateRepository: ShortcutHelperStateRepository, categoriesRepository: ShortcutHelperCategoriesRepository, ) { + private val systemsShortcutCategory = categoriesRepository.systemShortcutsCategory + private val multitaskingShortcutsCategory = categoriesRepository.multitaskingShortcutsCategory + private val imeShortcutsCategory = + categoriesRepository.imeShortcutsCategory.map { groupSubCategoriesInCategory(it) } + val shortcutCategories: Flow<List<ShortcutCategory>> = - stateRepository.state.map { state -> - when (state) { - is ShortcutHelperState.Active -> - listOf( - categoriesRepository.systemShortcutsCategory(), - categoriesRepository.multitaskingShortcutsCategory() - ) - is ShortcutHelperState.Inactive -> emptyList() - } + combine(systemsShortcutCategory, multitaskingShortcutsCategory, imeShortcutsCategory) { + shortcutCategories -> + shortcutCategories.filterNotNull() + } + + private fun groupSubCategoriesInCategory( + shortcutCategory: ShortcutCategory? + ): ShortcutCategory? { + if (shortcutCategory == null) { + return null } + val subCategoriesWithGroupedShortcuts = + shortcutCategory.subCategories.map { + ShortcutSubCategory( + label = it.label, + shortcuts = groupShortcutsInSubcategory(it.shortcuts) + ) + } + return ShortcutCategory( + type = shortcutCategory.type, + subCategories = subCategoriesWithGroupedShortcuts + ) + } + + private fun groupShortcutsInSubcategory(shortcuts: List<Shortcut>) = + shortcuts + .groupBy { it.label } + .entries + .map { (commonLabel, groupedShortcuts) -> + Shortcut(label = commonLabel, commands = groupedShortcuts.flatMap { it.commands }) + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt index 3d707f70538e..299628ee19f6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt @@ -26,6 +26,7 @@ import com.android.systemui.shared.system.QuickStepContract import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @SysUISingleton @@ -38,7 +39,7 @@ constructor( private val repository: ShortcutHelperStateRepository ) { - val state: Flow<ShortcutHelperState> = repository.state + val state: Flow<ShortcutHelperState> = repository.state.asStateFlow() fun onViewClosed() { repository.hide() diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt index c5e8d2c12fda..3ac7fa8f8ece 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyboard.shortcut.shared.model enum class ShortcutCategoryType { SYSTEM, MULTI_TASKING, + IME } data class ShortcutCategory( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index a7e263320745..5f6fe1c2130d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -105,6 +105,13 @@ const val LEGACY_UNLOCK_ANIMATION_DURATION_MS = 200L const val UNLOCK_ANIMATION_DURATION_MS = 167L /** + * If there are two different wallpapers on home and lock screen, duration and delay of the lock + * wallpaper fade out. + */ +const val LOCK_WALLPAPER_FADE_OUT_DURATION = 140L +const val LOCK_WALLPAPER_FADE_OUT_START_DELAY = 0L + +/** * How long the in-window launcher icon animation takes. This is used if the launcher is underneath * the lock screen and supports in-window animations. * @@ -115,23 +122,24 @@ const val LAUNCHER_ICONS_ANIMATION_DURATION_MS = 633L /** * How long to wait for the shade to get out of the way before starting the canned unlock animation. + * If there are two different wallpapers on home and lock screen, this is also the duration and + * delay of the home wallpaper fade in. */ const val LEGACY_CANNED_UNLOCK_START_DELAY = 100L -const val CANNED_UNLOCK_START_DELAY = 67L +const val CANNED_UNLOCK_START_DELAY = 25L /** * Duration for the alpha animation on the surface behind. This plays to fade in the surface during * a swipe to unlock (and to fade it back out if the swipe is cancelled). */ -const val LEGACY_SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 175L -const val SURFACE_BEHIND_FADE_OUT_DURATION_MS = 83L +const val SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 175L /** * Start delay for the surface behind animation, used so that the lockscreen can get out of the way * before the surface begins appearing. */ const val LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS = 75L -const val SURFACE_BEHIND_FADE_OUT_START_DELAY_MS = 0L +const val UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS = 67L /** * Initiates, controls, and ends the keyguard unlock animation. @@ -268,7 +276,8 @@ class KeyguardUnlockAnimationController @Inject constructor( @VisibleForTesting var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null private var surfaceBehindRemoteAnimationTargets: Array<RemoteAnimationTarget>? = null - private var wallpaperTargets: Array<RemoteAnimationTarget>? = null + private var openingWallpaperTargets: Array<RemoteAnimationTarget>? = null + private var closingWallpaperTargets: Array<RemoteAnimationTarget>? = null private var surfaceBehindRemoteAnimationStartTime: Long = 0 /** @@ -286,6 +295,8 @@ class KeyguardUnlockAnimationController @Inject constructor( var wallpaperCannedUnlockAnimator = ValueAnimator.ofFloat(0f, 1f) + var wallpaperFadeOutUnlockAnimator = ValueAnimator.ofFloat(1f, 0f) + /** * Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the * app/launcher behind the keyguard. @@ -335,7 +346,7 @@ class KeyguardUnlockAnimationController @Inject constructor( init { with(surfaceBehindAlphaAnimator) { - duration = surfaceBehindFadeOutDurationMs() + duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS interpolator = Interpolators.LINEAR addUpdateListener { valueAnimator: ValueAnimator -> surfaceBehindAlpha = valueAnimator.animatedValue as Float @@ -351,7 +362,8 @@ class KeyguardUnlockAnimationController @Inject constructor( if (surfaceBehindAlpha == 0f) { Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd") surfaceBehindRemoteAnimationTargets = null - wallpaperTargets = null + openingWallpaperTargets = null + closingWallpaperTargets = null keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation( false /* cancelled */) } else { @@ -367,8 +379,10 @@ class KeyguardUnlockAnimationController @Inject constructor( else LAUNCHER_ICONS_ANIMATION_DURATION_MS interpolator = if (fasterUnlockTransition()) Interpolators.LINEAR else Interpolators.ALPHA_OUT + if (fasterUnlockTransition()) startDelay = CANNED_UNLOCK_START_DELAY addUpdateListener { valueAnimator: ValueAnimator -> - setWallpaperAppearAmount(valueAnimator.animatedValue as Float) + setWallpaperAppearAmount( + valueAnimator.animatedValue as Float, openingWallpaperTargets) } addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { @@ -379,6 +393,18 @@ class KeyguardUnlockAnimationController @Inject constructor( }) } + if (fasterUnlockTransition()) { + with(wallpaperFadeOutUnlockAnimator) { + duration = LOCK_WALLPAPER_FADE_OUT_DURATION + startDelay = LOCK_WALLPAPER_FADE_OUT_START_DELAY + interpolator = Interpolators.LINEAR + addUpdateListener { valueAnimator: ValueAnimator -> + setWallpaperAppearAmount( + valueAnimator.animatedValue as Float, closingWallpaperTargets) + } + } + } + with(surfaceBehindEntryAnimator) { duration = unlockAnimationDurationMs() startDelay = surfaceBehindFadeOutStartDelayMs() @@ -546,7 +572,8 @@ class KeyguardUnlockAnimationController @Inject constructor( */ fun notifyStartSurfaceBehindRemoteAnimation( targets: Array<RemoteAnimationTarget>, - wallpapers: Array<RemoteAnimationTarget>, + openingWallpapers: Array<RemoteAnimationTarget>, + closingWallpapers: Array<RemoteAnimationTarget>, startTime: Long, requestedShowSurfaceBehindKeyguard: Boolean ) { @@ -556,7 +583,8 @@ class KeyguardUnlockAnimationController @Inject constructor( } surfaceBehindRemoteAnimationTargets = targets - wallpaperTargets = wallpapers + openingWallpaperTargets = openingWallpapers + closingWallpaperTargets = closingWallpapers surfaceBehindRemoteAnimationStartTime = startTime // If we specifically requested that the surface behind be made visible (vs. it being made @@ -720,8 +748,9 @@ class KeyguardUnlockAnimationController @Inject constructor( return@postDelayed } - if ((wallpaperTargets?.isNotEmpty() == true)) { + if ((openingWallpaperTargets?.isNotEmpty() == true)) { fadeInWallpaper() + if (fasterUnlockTransition()) fadeOutWallpaper() hideKeyguardViewAfterRemoteAnimation() } else { keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation( @@ -855,7 +884,8 @@ class KeyguardUnlockAnimationController @Inject constructor( /** * Scales in and translates up the surface behind the keyguard. This is used during unlock * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is - * cancelled). + * cancelled). When called with [wallpapers]=true, if there are different home and lock screen + * wallpapers, this transitions between the two wallpapers */ fun setSurfaceBehindAppearAmount(amount: Float, wallpapers: Boolean = true) { val animationAlpha = when { @@ -923,13 +953,27 @@ class KeyguardUnlockAnimationController @Inject constructor( } if (wallpapers) { - setWallpaperAppearAmount(amount) + if (!fasterUnlockTransition()) setWallpaperAppearAmount(amount, openingWallpaperTargets) + else { + // Use the amount to compute the fadeInAmount and fadeOutAmount of the home and lock + // screen wallpapers to manually imitate the canned unlock animation. + val total = (UNLOCK_ANIMATION_DURATION_MS + CANNED_UNLOCK_START_DELAY).toFloat() + val fadeInStart = CANNED_UNLOCK_START_DELAY / total + val fadeInAmount = maxOf(0f, (amount - fadeInStart) / (1f - fadeInStart)) + + val fadeOutStart = LOCK_WALLPAPER_FADE_OUT_START_DELAY / total + val fadeOutEnd = fadeOutStart + LOCK_WALLPAPER_FADE_OUT_DURATION / total + val fadeOutAmount = ((amount - fadeOutStart) / (fadeOutEnd - fadeOutStart)) + .coerceIn(0f, 1f) + + setWallpaperAppearAmount(fadeInAmount, openingWallpaperTargets) + setWallpaperAppearAmount(1 - fadeOutAmount, closingWallpaperTargets) + } } } - fun setWallpaperAppearAmount(amount: Float) { + fun setWallpaperAppearAmount(amount: Float, wallpaperTargets: Array<RemoteAnimationTarget>?) { val animationAlpha = amount - wallpaperTargets?.forEach { wallpaper -> // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is // unable to draw @@ -991,7 +1035,8 @@ class KeyguardUnlockAnimationController @Inject constructor( // That target is no longer valid since the animation finished, null it out. surfaceBehindRemoteAnimationTargets = null - wallpaperTargets = null + openingWallpaperTargets = null + if (fasterUnlockTransition()) closingWallpaperTargets = null playingCannedUnlockAnimation = false dismissAmountThresholdsReached = false @@ -1035,6 +1080,12 @@ class KeyguardUnlockAnimationController @Inject constructor( wallpaperCannedUnlockAnimator.start() } + private fun fadeOutWallpaper() { + Log.d(TAG, "fadeOutWallpaper") + wallpaperFadeOutUnlockAnimator.cancel() + wallpaperFadeOutUnlockAnimator.start() + } + private fun fadeOutSurfaceBehind() { Log.d(TAG, "fadeOutSurfaceBehind") surfaceBehindAlphaAnimator.cancel() @@ -1165,17 +1216,8 @@ class KeyguardUnlockAnimationController @Inject constructor( * Temporary method for b/298186160 * TODO (b/298186160) replace references with the constant itself when flag is removed */ - private fun surfaceBehindFadeOutDurationMs(): Long { - return if (fasterUnlockTransition()) SURFACE_BEHIND_FADE_OUT_DURATION_MS - else LEGACY_SURFACE_BEHIND_SWIPE_FADE_DURATION_MS - } - - /** - * Temporary method for b/298186160 - * TODO (b/298186160) replace references with the constant itself when flag is removed - */ private fun surfaceBehindFadeOutStartDelayMs(): Long { - return if (fasterUnlockTransition()) SURFACE_BEHIND_FADE_OUT_START_DELAY_MS + return if (fasterUnlockTransition()) UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 36b7ed26158f..1ea5d1c00561 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -42,6 +42,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground; import static com.android.systemui.Flags.refactorGetCurrentUser; +import static com.android.systemui.Flags.translucentOccludingActivityFix; import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS; import android.animation.Animator; @@ -1036,6 +1037,17 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, (int) (fullWidth - initialWidth) /* left */, fullWidth /* right */, mWindowCornerRadius, mWindowCornerRadius); + } else if (translucentOccludingActivityFix() + && mOccludingRemoteAnimationTarget != null + && mOccludingRemoteAnimationTarget.isTranslucent) { + // Animating in a transparent window looks really weird. Just let it be + // fullscreen and the app can do an internal animation if it wants to. + return new TransitionAnimator.State( + 0, + fullHeight, + 0, + fullWidth, + 0f, 0f); } else { final float initialHeight = fullHeight / 2f; final float initialWidth = fullWidth / 2f; @@ -1399,6 +1411,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final Lazy<DreamViewModel> mDreamViewModel; private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel; private RemoteAnimationTarget mRemoteAnimationTarget; + + /** + * The most recent RemoteAnimationTarget provided for an occluding activity animation. + */ + private RemoteAnimationTarget mOccludingRemoteAnimationTarget; private boolean mShowCommunalWhenUnoccluding = false; private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager; @@ -3143,9 +3160,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, w -> w.mode == RemoteAnimationTarget.MODE_OPENING).toArray( RemoteAnimationTarget[]::new); + RemoteAnimationTarget[] closingWallpapers = Arrays.stream(wallpapers).filter( + w -> w.mode == RemoteAnimationTarget.MODE_CLOSING).toArray( + RemoteAnimationTarget[]::new); + mKeyguardUnlockAnimationControllerLazy.get() .notifyStartSurfaceBehindRemoteAnimation( - openingApps, openingWallpapers, startTime, + openingApps, openingWallpapers, closingWallpapers, startTime, mSurfaceBehindRemoteAnimationRequested); } else { mInteractionJankMonitor.begin( @@ -3937,6 +3958,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { + // Save mRemoteAnimationTarget for reference in the animation controller. Needs to be + // called prior to super.onAnimationStart() since that's the call that eventually asks + // the animation controller to configure the animation state. + if (apps.length > 0) { + mOccludingRemoteAnimationTarget = apps[0]; + } + super.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback); mInteractionJankMonitor.begin( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt index 0863cd737529..80675d373b8e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt @@ -28,8 +28,6 @@ object BuiltInKeyguardQuickAffordanceKeys { const val CREATE_NOTE = "create_note" const val DO_NOT_DISTURB = "do_not_disturb" const val FLASHLIGHT = "flashlight" - // TODO(b/339667383): delete or properly implement this once a product decision is made - const val GLANCEABLE_HUB = "glanceable_hub" const val HOME_CONTROLS = "home" const val MUTE = "mute" const val QR_CODE_SCANNER = "qr_code_scanner" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt deleted file mode 100644 index 5d541260b05f..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.keyguard.data.quickaffordance - -import com.android.systemui.Flags -import com.android.systemui.animation.Expandable -import com.android.systemui.common.shared.model.ContentDescription -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.communal.data.repository.CommunalSceneRepository -import com.android.systemui.communal.shared.model.CommunalScenes -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.res.R -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf - -/** Shortcut that opens the glanceable hub. */ -// TODO(b/339667383): delete or properly implement this once a product decision is made -@SysUISingleton -class GlanceableHubQuickAffordanceConfig -@Inject -constructor( - private val communalRepository: CommunalSceneRepository, -) : KeyguardQuickAffordanceConfig { - - override val key: String = BuiltInKeyguardQuickAffordanceKeys.GLANCEABLE_HUB - - override fun pickerName(): String = "Glanceable hub" - - override val pickerIconResourceId = R.drawable.ic_widgets - - override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> by lazy { - if (Flags.glanceableHubShortcutButton()) { - val contentDescription = ContentDescription.Loaded(pickerName()) - val icon = Icon.Resource(pickerIconResourceId, contentDescription) - flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon)) - } else { - flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) - } - } - - override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { - return KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice - } - - override fun onTriggered( - expandable: Expandable? - ): KeyguardQuickAffordanceConfig.OnTriggeredResult { - communalRepository.changeScene(CommunalScenes.Communal, null) - return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt index 93296f0ca24b..45561959a7df 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt @@ -36,7 +36,6 @@ interface KeyguardDataQuickAffordanceModule { camera: CameraQuickAffordanceConfig, doNotDisturb: DoNotDisturbQuickAffordanceConfig, flashlight: FlashlightQuickAffordanceConfig, - glanceableHub: GlanceableHubQuickAffordanceConfig, home: HomeControlsKeyguardQuickAffordanceConfig, mute: MuteQuickAffordanceConfig, quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, @@ -47,7 +46,6 @@ interface KeyguardDataQuickAffordanceModule { camera, doNotDisturb, flashlight, - glanceableHub, home, mute, quickAccessWallet, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt index 0748979a2465..796374aadc1a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt @@ -20,7 +20,6 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context import android.content.IntentFilter import android.content.SharedPreferences -import com.android.systemui.Flags import com.android.systemui.backup.BackupHelper import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging @@ -30,7 +29,6 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker -import com.android.systemui.util.settings.SystemSettings import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose @@ -52,7 +50,6 @@ constructor( @Application private val context: Context, private val userFileManager: UserFileManager, private val userTracker: UserTracker, - private val systemSettings: SystemSettings, broadcastDispatcher: BroadcastDispatcher, ) : KeyguardQuickAffordanceSelectionManager { @@ -73,22 +70,6 @@ constructor( } private val defaults: Map<String, List<String>> by lazy { - // Quick hack to allow testing out a lock screen shortcut to open the glanceable hub. This - // flag will not be rolled out and is only used for local testing. - // TODO(b/339667383): delete or properly implement this once a product decision is made - if (Flags.glanceableHubShortcutButton()) { - if (systemSettings.getBool("open_hub_chip_replace_home_controls", false)) { - return@lazy mapOf( - "bottom_start" to listOf("glanceable_hub"), - "bottom_end" to listOf("create_note") - ) - } else { - return@lazy mapOf( - "bottom_start" to listOf("home"), - "bottom_end" to listOf("glanceable_hub") - ) - } - } context.resources .getStringArray(R.array.config_keyguardQuickAffordanceDefaults) .associate { item -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index ab1194e66275..22ab82b383a7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -51,7 +51,6 @@ import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.notification.NotificationUtils.interpolate import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine -import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -251,13 +250,17 @@ constructor( /** Keyguard can be clipped at the top as the shade is dragged */ val topClippingBounds: Flow<Int?> by lazy { - repository.topClippingBounds - .sampleFilter( + combineTransform( keyguardTransitionInteractor .transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE) - .onStart { emit(0f) } - ) { goneValue -> - goneValue != 1f + .map { it == 1f } + .onStart { emit(false) } + .distinctUntilChanged(), + repository.topClippingBounds + ) { isGone, topClippingBounds -> + if (!isGone) { + emit(topClippingBounds) + } } .distinctUntilChanged() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt index bb6215a8b215..7a06d2fe9254 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt @@ -32,6 +32,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.res.R +import com.android.systemui.shade.PulsingGestureListener import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -51,10 +52,10 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -/** Business logic for use-cases related to the keyguard long-press feature. */ +/** Business logic for use-cases related to top-level touch handling in the lock screen. */ @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton -class KeyguardLongPressInteractor +class KeyguardTouchHandlingInteractor @Inject constructor( @Application private val appContext: Context, @@ -65,6 +66,7 @@ constructor( private val featureFlags: FeatureFlags, broadcastDispatcher: BroadcastDispatcher, private val accessibilityManager: AccessibilityManagerWrapper, + private val pulsingGestureListener: PulsingGestureListener, ) { /** Whether the long-press handling feature should be enabled. */ val isLongPressHandlingEnabled: StateFlow<Boolean> = @@ -166,6 +168,16 @@ constructor( _shouldOpenSettings.value = false } + /** Notifies that the lockscreen has been clicked at position [x], [y]. */ + fun onClick(x: Float, y: Float) { + pulsingGestureListener.onSingleTapUp(x, y) + } + + /** Notifies that the lockscreen has been double clicked. */ + fun onDoubleClick() { + pulsingGestureListener.onDoubleTapEvent() + } + private fun showSettings() { _shouldOpenSettings.value = true } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt index 09fe067f7724..057b4f9a671d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt @@ -22,7 +22,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launch import com.android.systemui.common.ui.view.LongPressHandlingView -import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager @@ -39,7 +39,7 @@ object KeyguardLongPressViewBinder { @JvmStatic fun bind( view: LongPressHandlingView, - viewModel: KeyguardLongPressViewModel, + viewModel: KeyguardTouchHandlingViewModel, onSingleTap: () -> Unit, falsingManager: FalsingManager, ) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt index fa5756522a6a..4150ceb8aa31 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt @@ -26,9 +26,9 @@ import com.android.app.tracing.coroutines.launch import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.common.ui.binder.TextViewBinder -import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils.LAUNCH_SOURCE_KEYGUARD import com.android.systemui.lifecycle.repeatWhenAttached @@ -44,7 +44,7 @@ object KeyguardSettingsViewBinder { fun bind( view: View, viewModel: KeyguardSettingsMenuViewModel, - longPressViewModel: KeyguardLongPressViewModel, + touchHandlingViewModel: KeyguardTouchHandlingViewModel, rootViewModel: KeyguardRootViewModel?, vibratorHelper: VibratorHelper, activityStarter: ActivityStarter @@ -97,7 +97,7 @@ object KeyguardSettingsViewBinder { val hitRect = Rect() view.getHitRect(hitRect) if (!hitRect.contains(point.x, point.y)) { - longPressViewModel.onTouchedOutside() + touchHandlingViewModel.onTouchedOutside() } } } 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 777c873e47f0..4f0ac42a0e87 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 @@ -197,8 +197,7 @@ constructor( initiallySelectedSlotId = bundle.getString( KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID, - ) - ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + ) ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance, ) } else { @@ -230,8 +229,7 @@ constructor( val previewContext = display?.let { ContextThemeWrapper(context.createDisplayContext(it), context.getTheme()) - } - ?: context + } ?: context val rootView = FrameLayout(previewContext) @@ -318,8 +316,8 @@ constructor( */ private fun setUpSmartspace(previewContext: Context, parentView: ViewGroup) { if ( - !lockscreenSmartspaceController.isEnabled() || - !lockscreenSmartspaceController.isDateWeatherDecoupled() + !lockscreenSmartspaceController.isEnabled || + !lockscreenSmartspaceController.isDateWeatherDecoupled ) { return } @@ -654,6 +652,7 @@ constructor( clockController.clock = clock } } + private fun onClockChanged() { if (MigrateClocksToBlueprint.isEnabled) { return diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt index 32e76d0b24ff..5cd5172d1851 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt @@ -34,9 +34,9 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder -import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper @@ -48,7 +48,7 @@ class DefaultSettingsPopupMenuSection constructor( @Main private val resources: Resources, private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel, - private val keyguardLongPressViewModel: KeyguardLongPressViewModel, + private val keyguardTouchHandlingViewModel: KeyguardTouchHandlingViewModel, private val keyguardRootViewModel: KeyguardRootViewModel, private val vibratorHelper: VibratorHelper, private val activityStarter: ActivityStarter, @@ -76,7 +76,7 @@ constructor( KeyguardSettingsViewBinder.bind( constraintLayout.requireViewById<View>(R.id.keyguard_settings_button), keyguardSettingsMenuViewModel, - keyguardLongPressViewModel, + keyguardTouchHandlingViewModel, keyguardRootViewModel, vibratorHelper, activityStarter, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt index a17c5e538382..b33d55244037 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt @@ -35,7 +35,7 @@ constructor( ) : KeyguardSection() { override fun addViews(constraintLayout: ConstraintLayout) { if (!MigrateClocksToBlueprint.isEnabled) return - if (smartspaceController.isEnabled()) return + if (smartspaceController.isEnabled) return constraintLayout.findViewById<View?>(R.id.keyguard_slice_view)?.let { (it.parent as ViewGroup).removeView(it) @@ -47,7 +47,7 @@ constructor( override fun applyConstraints(constraintSet: ConstraintSet) { if (!MigrateClocksToBlueprint.isEnabled) return - if (smartspaceController.isEnabled()) return + if (smartspaceController.isEnabled) return constraintSet.apply { connect( @@ -82,7 +82,7 @@ constructor( override fun removeViews(constraintLayout: ConstraintLayout) { if (!MigrateClocksToBlueprint.isEnabled) return - if (smartspaceController.isEnabled()) return + if (smartspaceController.isEnabled) return constraintLayout.removeView(R.id.keyguard_slice_view) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt index 3e6f8e68891a..6fe51ae885be 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt @@ -44,7 +44,7 @@ constructor( private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor, private val bottomAreaInteractor: KeyguardBottomAreaInteractor, private val burnInHelperWrapper: BurnInHelperWrapper, - private val longPressViewModel: KeyguardLongPressViewModel, + private val keyguardTouchHandlingViewModel: KeyguardTouchHandlingViewModel, val settingsMenuViewModel: KeyguardSettingsMenuViewModel, ) { data class PreviewMode( @@ -162,7 +162,7 @@ constructor( * the lock screen settings menu item pop-up. */ fun onTouchedOutsideLockScreenSettingsMenu() { - longPressViewModel.onTouchedOutside() + keyguardTouchHandlingViewModel.onTouchedOutside() } private fun button( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt index 66ceded2040d..36a342b13df7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt @@ -17,10 +17,10 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.res.R import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text -import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTouchHandlingInteractor +import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -28,7 +28,7 @@ import kotlinx.coroutines.flow.Flow class KeyguardSettingsMenuViewModel @Inject constructor( - private val interactor: KeyguardLongPressInteractor, + private val interactor: KeyguardTouchHandlingInteractor, ) { val isVisible: Flow<Boolean> = interactor.isMenuVisible val shouldOpenSettings: Flow<Boolean> = interactor.shouldOpenSettings diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt index c0b1f9543abf..e30ddc69b19d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt @@ -40,12 +40,12 @@ constructor( smartspaceInteractor: KeyguardSmartspaceInteractor, ) { /** Whether the smartspace section is available in the build. */ - val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled() + val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled /** Whether the weather area is available in the build. */ private val isWeatherEnabled: StateFlow<Boolean> = smartspaceInteractor.isWeatherEnabled /** Whether the data and weather areas are decoupled in the build. */ - val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled() + val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled /** Whether the date area should be visible. */ val isDateVisible: StateFlow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt index c73931a12455..f1cbf256a00f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt @@ -18,16 +18,16 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTouchHandlingInteractor import javax.inject.Inject import kotlinx.coroutines.flow.Flow -/** Models UI state to support the lock screen long-press feature. */ +/** Models UI state to support top-level touch handling in the lock screen. */ @SysUISingleton -class KeyguardLongPressViewModel +class KeyguardTouchHandlingViewModel @Inject constructor( - private val interactor: KeyguardLongPressInteractor, + private val interactor: KeyguardTouchHandlingInteractor, ) { /** Whether the long-press handling feature should be enabled. */ @@ -45,4 +45,14 @@ constructor( fun onTouchedOutside() { interactor.onTouchedOutside() } + + /** Notifies that the lockscreen has been clicked at position [x], [y]. */ + fun onClick(x: Float, y: Float) { + interactor.onClick(x, y) + } + + /** Notifies that the lockscreen has been double clicked. */ + fun onDoubleClick() { + interactor.onDoubleClick() + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt index b33eaa2c691b..1de0abeb931b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt @@ -44,7 +44,7 @@ constructor( clockInteractor: KeyguardClockInteractor, private val interactor: KeyguardBlueprintInteractor, private val authController: AuthController, - val longPress: KeyguardLongPressViewModel, + val touchHandling: KeyguardTouchHandlingViewModel, val shadeInteractor: ShadeInteractor, @Application private val applicationScope: CoroutineScope, unfoldTransitionInteractor: UnfoldTransitionInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index 10cfd6ba44d8..630dcca56dd9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -53,7 +53,7 @@ constructor( deviceEntryInteractor: DeviceEntryInteractor, communalInteractor: CommunalInteractor, shadeInteractor: ShadeInteractor, - val longPress: KeyguardLongPressViewModel, + val touchHandling: KeyguardTouchHandlingViewModel, val notifications: NotificationsPlaceholderViewModel, ) { val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index c7fde48dc0ea..52b0b87ddb58 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -674,4 +674,11 @@ public class LogModule { return factory.create("DeviceEntryIconLog", 100); } + /** Provides a {@link LogBuffer} for use by the volume loggers. */ + @Provides + @SysUISingleton + @VolumeLog + public static LogBuffer provideVolumeLogBuffer(LogBufferFactory factory) { + return factory.create("VolumeLog", 50); + } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/VolumeLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/VolumeLog.kt new file mode 100644 index 000000000000..bc3858a200dc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/VolumeLog.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger + +import javax.inject.Qualifier + +/** A [com.android.systemui.log.LogBuffer] for volume. */ +@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class VolumeLog diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index e7c2a454e16c..bda006915c94 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -17,6 +17,7 @@ package com.android.systemui.media; import android.annotation.Nullable; +import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; @@ -123,9 +124,13 @@ public class RingtonePlayer implements CoreStartable { boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig) throws RemoteException { if (LOGD) { - Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid=" - + Binder.getCallingUid() + ")"); + Log.d(TAG, "play(token=" + token + ", uri=" + uri + + ", uid=" + Binder.getCallingUid() + + ") uriUserId=" + ContentProvider.getUserIdFromUri(uri) + + " callingUserId=" + Binder.getCallingUserHandle().getIdentifier()); } + enforceUriUserId(uri); + Client client; synchronized (mClients) { client = mClients.get(token); @@ -207,6 +212,7 @@ public class RingtonePlayer implements CoreStartable { @Override public String getTitle(Uri uri) { + enforceUriUserId(uri); final UserHandle user = Binder.getCallingUserHandle(); return Ringtone.getTitle(getContextForUser(user), uri, false /*followSettingsUri*/, false /*allowRemote*/); @@ -239,6 +245,25 @@ public class RingtonePlayer implements CoreStartable { } throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri); } + + /** + * Must be called from the Binder calling thread. + * Ensures caller is from the same userId as the content they're trying to access. + * @param uri the URI to check + * @throws SecurityException when non-system call or userId in uri differs from the + * caller's userId + */ + private void enforceUriUserId(Uri uri) throws SecurityException { + final int uriUserId = ContentProvider.getUserIdFromUri(uri); + final int callerUserId = Binder.getCallingUserHandle().getIdentifier(); + // for a non-system call, verify the URI to play belongs to the same user as the caller + if (UserHandle.isApp(Binder.getCallingUid()) && uriUserId != callerUserId) { + throw new SecurityException("Illegal access to uri=" + uri + + " content associated with user=" + uriUserId + + ", request originates from user=" + callerUserId); + } + } + }; private Context getContextForUser(UserHandle user) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index 846c596a238d..69a157f19ea1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -585,7 +585,8 @@ public class MediaControlPanel { /* intentSentUiThreadCallback = */ null, buildLaunchAnimatorController(mMediaViewHolder.getPlayer()), /* fillIntent = */ null, - /* extraOptions = */ null); + /* extraOptions = */ null, + /* customMessage */ null); } else { try { ActivityOptions opts = ActivityOptions.makeBasic(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index 2e5ff9ddb477..b39315575882 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -27,11 +27,11 @@ import static com.android.systemui.accessibility.SystemActions.SYSTEM_ACTION_ID_ import static com.android.systemui.accessibility.SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT; import android.content.ContentResolver; import android.content.Context; @@ -74,10 +74,10 @@ import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.rotation.RotationPolicyUtil; +import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; -import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 69aa450a9d4c..89dce032c727 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -48,8 +48,8 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_I import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; -import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode; import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_WINDOW_STATE; import static com.android.systemui.statusbar.phone.CentralSurfaces.dumpBarTransitions; import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay; @@ -137,6 +137,7 @@ import com.android.systemui.shared.navigationbar.RegionSamplingHelper; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.shared.rotation.RotationPolicyUtil; +import com.android.systemui.shared.statusbar.phone.BarTransitions; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -149,7 +150,6 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.AutoHideController; -import com.android.systemui.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index a601d7f25b6e..c801662b2a66 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -19,7 +19,7 @@ package com.android.systemui.navigationbar; import androidx.annotation.Nullable; import com.android.internal.statusbar.RegisterStatusBarResult; -import com.android.systemui.statusbar.phone.BarTransitions; +import com.android.systemui.shared.statusbar.phone.BarTransitions; /** A controller to handle navigation bars. */ public interface NavigationBarController { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt index e73b078a3d88..06a78c59ef04 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt @@ -18,7 +18,7 @@ package com.android.systemui.navigationbar import com.android.internal.statusbar.RegisterStatusBarResult import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.phone.BarTransitions +import com.android.systemui.shared.statusbar.phone.BarTransitions import javax.inject.Inject /** A no-op version of [NavigationBarController] for variants like Arc and TV. */ diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java index 1c2a0871f3f9..f0207aa69bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java @@ -56,11 +56,11 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.DisplayTracker; +import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.AutoHideController; -import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.settings.SecureSettings; @@ -431,6 +431,8 @@ public class NavigationBarControllerImpl implements NavigationBar navBar = mNavigationBars.get(displayId); if (navBar != null) { navBar.checkNavBarModes(); + } else { + mTaskbarDelegate.checkNavBarModes(); } } @@ -439,6 +441,8 @@ public class NavigationBarControllerImpl implements NavigationBar navBar = mNavigationBars.get(displayId); if (navBar != null) { navBar.finishBarAnimations(); + } else { + mTaskbarDelegate.finishBarAnimations(); } } @@ -447,6 +451,8 @@ public class NavigationBarControllerImpl implements NavigationBar navBar = mNavigationBars.get(displayId); if (navBar != null) { navBar.touchAutoDim(); + } else { + mTaskbarDelegate.touchAutoDim(); } } @@ -455,6 +461,8 @@ public class NavigationBarControllerImpl implements NavigationBar navBar = mNavigationBars.get(displayId); if (navBar != null) { navBar.transitionTo(barMode, animate); + } else { + mTaskbarDelegate.transitionTo(barMode, animate); } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java index 201e58625098..54425985cb8c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java @@ -28,7 +28,7 @@ import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarSc import com.android.systemui.navigationbar.buttons.ButtonDispatcher; import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; -import com.android.systemui.statusbar.phone.BarTransitions; +import com.android.systemui.shared.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.LightBarTransitionsController; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index b360af098fa0..d022c1c2e948 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -24,6 +24,7 @@ import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_B import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static com.android.systemui.navigationbar.NavBarHelper.transitionMode; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY; @@ -34,7 +35,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_I import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; -import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import android.app.StatusBarManager; import android.app.StatusBarManager.WindowVisibleState; @@ -63,13 +63,16 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.statusbar.phone.BarTransitions; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LightBarTransitionsController; @@ -117,6 +120,11 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, boolean longPressHomeEnabled) { updateAssistantAvailability(available, longPressHomeEnabled); } + + @Override + public void updateWallpaperVisibility(boolean visible, int displayId) { + updateWallpaperVisible(displayId, visible); + } }; private int mDisabledFlags; private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING; @@ -150,6 +158,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() { @Override public void synchronizeState() { + checkNavBarModes(); } @Override @@ -165,11 +174,13 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private BackAnimation mBackAnimation; - private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final StatusBarStateController mStatusBarStateController; @Inject public TaskbarDelegate(Context context, LightBarTransitionsController.Factory lightBarTransitionsControllerFactory, - StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + StatusBarKeyguardViewManager statusBarKeyguardViewManager, + StatusBarStateController statusBarStateController) { mLightBarTransitionsControllerFactory = lightBarTransitionsControllerFactory; mContext = context; @@ -179,6 +190,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, }; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mStatusBarKeyguardViewManager.setTaskbarDelegate(this); + mStatusBarStateController = statusBarStateController; } public void setDependencies(CommandQueue commandQueue, @@ -324,6 +336,68 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, return (mSysUiState.getFlags() & View.STATUS_BAR_DISABLE_RECENT) == 0; } + void onTransitionModeUpdated(int barMode, boolean checkBarModes) { + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().onTransitionModeUpdated(barMode, checkBarModes); + } catch (RemoteException e) { + Log.e(TAG, "onTransitionModeUpdated() failed, barMode: " + barMode, e); + } + } + + void checkNavBarModes() { + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().checkNavBarModes(); + } catch (RemoteException e) { + Log.e(TAG, "checkNavBarModes() failed", e); + } + } + + void finishBarAnimations() { + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().finishBarAnimations(); + } catch (RemoteException e) { + Log.e(TAG, "finishBarAnimations() failed", e); + } + } + + void touchAutoDim() { + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + int state = mStatusBarStateController.getState(); + boolean shouldReset = + state != StatusBarState.KEYGUARD && state != StatusBarState.SHADE_LOCKED; + mOverviewProxyService.getProxy().touchAutoDim(shouldReset); + } catch (RemoteException e) { + Log.e(TAG, "touchAutoDim() failed", e); + } + } + + void transitionTo(@BarTransitions.TransitionMode int barMode, boolean animate) { + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().transitionTo(barMode, animate); + } catch (RemoteException e) { + Log.e(TAG, "transitionTo() failed, barMode: " + barMode, e); + } + } private void updateAssistantAvailability(boolean assistantAvailable, boolean longPressHomeEnabled) { if (mOverviewProxyService.getProxy() == null) { @@ -338,6 +412,18 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } } + private void updateWallpaperVisible(int displayId, boolean visible) { + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().updateWallpaperVisibility(displayId, visible); + } catch (RemoteException e) { + Log.e(TAG, "updateWallpaperVisibility() failed, visible: " + visible, e); + } + } + @Override public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { @@ -465,9 +551,11 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private boolean updateTransitionMode(int barMode) { if (mTransitionMode != barMode) { mTransitionMode = barMode; + onTransitionModeUpdated(barMode, true); if (mAutoHideController != null) { mAutoHideController.touchAutoHide(); } + return true; } return false; diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt new file mode 100644 index 000000000000..007ec3a911e1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalFoundationApi::class) + +package com.android.systemui.qs.panels.ui.compose + +import android.content.ClipData +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.draganddrop.dragAndDropSource +import androidx.compose.foundation.draganddrop.dragAndDropTarget +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draganddrop.DragAndDropEvent +import androidx.compose.ui.draganddrop.DragAndDropTarget +import androidx.compose.ui.draganddrop.DragAndDropTransferData +import androidx.compose.ui.draganddrop.mimeTypes +import com.android.systemui.qs.pipeline.shared.TileSpec + +@Composable +fun rememberDragAndDropState(listState: EditTileListState): DragAndDropState { + val sourceSpec: MutableState<TileSpec?> = remember { mutableStateOf(null) } + return remember(listState) { DragAndDropState(sourceSpec, listState) } +} + +/** + * Holds the [TileSpec] of the tile being moved and modify the [EditTileListState] based on drag and + * drop events. + */ +class DragAndDropState( + val sourceSpec: MutableState<TileSpec?>, + private val listState: EditTileListState +) { + /** Returns index of the dragged tile if it's present in the list. Returns -1 if not. */ + fun currentPosition(): Int { + return sourceSpec.value?.let { listState.indexOf(it) } ?: -1 + } + + fun isMoving(tileSpec: TileSpec): Boolean { + return sourceSpec.value?.let { it == tileSpec } ?: false + } + + fun onStarted(spec: TileSpec) { + sourceSpec.value = spec + } + + fun onMoved(targetSpec: TileSpec) { + sourceSpec.value?.let { listState.move(it, targetSpec) } + } + + fun onDrop() { + sourceSpec.value = null + } +} + +/** + * Registers a tile as a [DragAndDropTarget] to receive drag events and update the + * [DragAndDropState] with the tile's position, which can be used to insert a temporary placeholder. + * + * @param dragAndDropState The [DragAndDropState] using the tiles list + * @param tileSpec The [TileSpec] of the tile + * @param acceptDrops Whether the tile should accept a drop based on a given [TileSpec] + * @param onDrop Action to be executed when a [TileSpec] is dropped on the tile + */ +@Composable +fun Modifier.dragAndDropTile( + dragAndDropState: DragAndDropState, + tileSpec: TileSpec, + acceptDrops: (TileSpec) -> Boolean, + onDrop: (TileSpec, Int) -> Unit, +): Modifier { + val target = + remember(dragAndDropState) { + object : DragAndDropTarget { + override fun onDrop(event: DragAndDropEvent): Boolean { + return dragAndDropState.sourceSpec.value?.let { + onDrop(it, dragAndDropState.currentPosition()) + dragAndDropState.onDrop() + true + } ?: false + } + + override fun onEntered(event: DragAndDropEvent) { + dragAndDropState.onMoved(tileSpec) + } + } + } + return dragAndDropTarget( + shouldStartDragAndDrop = { event -> + event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) && + dragAndDropState.sourceSpec.value?.let { acceptDrops(it) } ?: false + }, + target = target, + ) +} + +/** + * Registers a tile list as a [DragAndDropTarget] to receive drop events. Use this on list + * containers to catch drops outside of tiles. + * + * @param dragAndDropState The [DragAndDropState] using the tiles list + * @param acceptDrops Whether the tile should accept a drop based on a given [TileSpec] + * @param onDrop Action to be executed when a [TileSpec] is dropped on the tile + */ +@Composable +fun Modifier.dragAndDropTileList( + dragAndDropState: DragAndDropState, + acceptDrops: (TileSpec) -> Boolean, + onDrop: (TileSpec, Int) -> Unit, +): Modifier { + val target = + remember(dragAndDropState) { + object : DragAndDropTarget { + override fun onDrop(event: DragAndDropEvent): Boolean { + return dragAndDropState.sourceSpec.value?.let { + onDrop(it, dragAndDropState.currentPosition()) + dragAndDropState.onDrop() + true + } ?: false + } + } + } + return dragAndDropTarget( + target = target, + shouldStartDragAndDrop = { event -> + event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) && + dragAndDropState.sourceSpec.value?.let { acceptDrops(it) } ?: false + }, + ) +} + +fun Modifier.dragAndDropTileSource( + tileSpec: TileSpec, + onTap: (TileSpec) -> Unit, + dragAndDropState: DragAndDropState +): Modifier { + return dragAndDropSource { + detectTapGestures( + onTap = { onTap(tileSpec) }, + onLongPress = { + dragAndDropState.onStarted(tileSpec) + + // The tilespec from the ClipData transferred isn't actually needed as we're moving + // a tile within the same application. We're using a custom MIME type to limit the + // drag event to QS. + startTransfer( + DragAndDropTransferData( + ClipData( + QsDragAndDrop.CLIPDATA_LABEL, + arrayOf(QsDragAndDrop.TILESPEC_MIME_TYPE), + ClipData.Item(tileSpec.spec) + ) + ) + ) + } + ) + } +} + +private object QsDragAndDrop { + const val CLIPDATA_LABEL = "tilespec" + const val TILESPEC_MIME_TYPE = "qstile/tilespec" +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt new file mode 100644 index 000000000000..482c498d37d7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.runtime.toMutableStateList +import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec + +@Composable +fun rememberEditListState( + tiles: List<EditTileViewModel>, +): EditTileListState { + return remember(tiles) { EditTileListState(tiles) } +} + +/** Holds the temporary state of the tile list during a drag movement where we move tiles around. */ +class EditTileListState(tiles: List<EditTileViewModel>) { + val tiles: SnapshotStateList<EditTileViewModel> = tiles.toMutableStateList() + + fun move(tileSpec: TileSpec, target: TileSpec) { + val fromIndex = indexOf(tileSpec) + val toIndex = indexOf(target) + + if (fromIndex == -1 || toIndex == -1 || fromIndex == toIndex) { + return + } + + val isMovingToCurrent = tiles[toIndex].isCurrent + tiles.apply { add(toIndex, removeAt(fromIndex).copy(isCurrent = isMovingToCurrent)) } + } + + fun indexOf(tileSpec: TileSpec): Int { + return tiles.indexOfFirst { it.tileSpec == tileSpec } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt index 7f5e474f2fa7..092ad44d289b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt @@ -110,12 +110,15 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition tiles: List<EditTileViewModel>, modifier: Modifier, onAddTile: (TileSpec, Int) -> Unit, - onRemoveTile: (TileSpec) -> Unit + onRemoveTile: (TileSpec) -> Unit, ) { val columns by viewModel.columns.collectAsStateWithLifecycle() val showLabels by viewModel.showLabels.collectAsStateWithLifecycle() - val (currentTiles, otherTiles) = tiles.partition { it.isCurrent } + val listState = rememberEditListState(tiles) + val dragAndDropState = rememberDragAndDropState(listState) + + val (currentTiles, otherTiles) = listState.tiles.partition { it.isCurrent } val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState { onAddTile(it, CurrentTilesInteractor.POSITION_AT_END) } @@ -156,20 +159,24 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition largeTileHeight = largeTileHeight, iconTileHeight = iconTileHeight, tilePadding = tilePadding, - onRemoveTile = onRemoveTile, + onAdd = onAddTile, + onRemove = onRemoveTile, isIconOnly = viewModel::isIconTile, columns = columns, showLabels = showLabels, + dragAndDropState = dragAndDropState, ) AvailableTiles( - tiles = otherTiles, + tiles = otherTiles.filter { !dragAndDropState.isMoving(it.tileSpec) }, largeTileHeight = largeTileHeight, iconTileHeight = iconTileHeight, tilePadding = tilePadding, addTileToEnd = addTileToEnd, + onRemove = onRemoveTile, isIconOnly = viewModel::isIconTile, showLabels = showLabels, columns = columns, + dragAndDropState = dragAndDropState, ) } } @@ -194,10 +201,12 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition largeTileHeight: Dp, iconTileHeight: Dp, tilePadding: Dp, - onRemoveTile: (TileSpec) -> Unit, + onAdd: (TileSpec, Int) -> Unit, + onRemove: (TileSpec) -> Unit, isIconOnly: (TileSpec) -> Boolean, showLabels: Boolean, columns: Int, + dragAndDropState: DragAndDropState, ) { val (smallTiles, largeTiles) = tiles.partition { isIconOnly(it.tileSpec) } @@ -207,29 +216,40 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition CurrentTilesContainer { TileLazyGrid( columns = GridCells.Fixed(columns), - modifier = Modifier.height(largeGridHeight), + modifier = + Modifier.height(largeGridHeight) + .dragAndDropTileList(dragAndDropState, { !isIconOnly(it) }, onAdd) ) { editTiles( - largeTiles, - ClickAction.REMOVE, - onRemoveTile, - { false }, - indicatePosition = true + tiles = largeTiles, + clickAction = ClickAction.REMOVE, + onClick = onRemove, + isIconOnly = { false }, + dragAndDropState = dragAndDropState, + acceptDrops = { !isIconOnly(it) }, + onDrop = onAdd, + indicatePosition = true, ) } } + CurrentTilesContainer { TileLazyGrid( columns = GridCells.Fixed(columns), - modifier = Modifier.height(smallGridHeight), + modifier = + Modifier.height(smallGridHeight) + .dragAndDropTileList(dragAndDropState, { isIconOnly(it) }, onAdd) ) { editTiles( - smallTiles, - ClickAction.REMOVE, - onRemoveTile, - { true }, + tiles = smallTiles, + clickAction = ClickAction.REMOVE, + onClick = onRemove, + isIconOnly = { true }, showLabels = showLabels, - indicatePosition = true + dragAndDropState = dragAndDropState, + acceptDrops = { isIconOnly(it) }, + onDrop = onAdd, + indicatePosition = true, ) } } @@ -242,9 +262,11 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition iconTileHeight: Dp, tilePadding: Dp, addTileToEnd: (TileSpec) -> Unit, + onRemove: (TileSpec) -> Unit, isIconOnly: (TileSpec) -> Boolean, showLabels: Boolean, columns: Int, + dragAndDropState: DragAndDropState, ) { val (tilesStock, tilesCustom) = tiles.partition { it.appName == null } val (smallTiles, largeTiles) = tilesStock.partition { isIconOnly(it.tileSpec) } @@ -258,13 +280,27 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition val gridHeight = largeGridHeight + smallGridHeight + largeGridHeightCustom + (tilePadding * 2) + val onDrop: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, _ -> + onRemove(tileSpec) + } + AvailableTilesContainer { TileLazyGrid( columns = GridCells.Fixed(columns), - modifier = Modifier.height(gridHeight), + modifier = + Modifier.height(gridHeight) + .dragAndDropTileList(dragAndDropState, { true }, onDrop) ) { // Large tiles - editTiles(largeTiles, ClickAction.ADD, addTileToEnd, isIconOnly) + editTiles( + largeTiles, + ClickAction.ADD, + addTileToEnd, + isIconOnly, + dragAndDropState, + acceptDrops = { true }, + onDrop = onDrop, + ) fillUpRow(nTiles = largeTiles.size, columns = columns / 2) // Small tiles @@ -273,7 +309,10 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition ClickAction.ADD, addTileToEnd, isIconOnly, - showLabels = showLabels + showLabels = showLabels, + dragAndDropState = dragAndDropState, + acceptDrops = { true }, + onDrop = onDrop, ) fillUpRow(nTiles = smallTiles.size, columns = columns) @@ -283,7 +322,10 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition ClickAction.ADD, addTileToEnd, isIconOnly, - showLabels = showLabels + showLabels = showLabels, + dragAndDropState = dragAndDropState, + acceptDrops = { true }, + onDrop = onDrop, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt index bbb98d3ca277..0bb4cfa327a9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt @@ -74,12 +74,12 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Expandable +import com.android.compose.modifiers.thenIf import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.common.ui.compose.load import com.android.systemui.plugins.qs.QSTile -import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileUiState import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel @@ -114,6 +114,7 @@ fun Tile( showLabels = showLabels, label = state.label.toString(), iconOnly = iconOnly, + clickEnabled = true, onClick = tile::onClick, onLongClick = tile::onLongClick, modifier = modifier, @@ -127,6 +128,7 @@ fun Tile( secondaryLabel = state.secondaryLabel.toString(), icon = icon, colors = colors, + clickEnabled = true, onClick = tile::onSecondaryClick, onLongClick = tile::onLongClick, ) @@ -140,7 +142,7 @@ private fun TileContainer( showLabels: Boolean, label: String, iconOnly: Boolean, - clickEnabled: Boolean = true, + clickEnabled: Boolean = false, onClick: (Expandable) -> Unit = {}, onLongClick: (Expandable) -> Unit = {}, modifier: Modifier = Modifier, @@ -168,11 +170,12 @@ private fun TileContainer( Box( modifier = Modifier.fillMaxSize() - .combinedClickable( - enabled = clickEnabled, - onClick = { onClick(it) }, - onLongClick = { onLongClick(it) } - ) + .thenIf(clickEnabled) { + Modifier.combinedClickable( + onClick = { onClick(it) }, + onLongClick = { onLongClick(it) } + ) + } .tilePadding(), ) { content() @@ -197,7 +200,7 @@ private fun LargeTileContent( secondaryLabel: String?, icon: Icon, colors: TileColors, - clickEnabled: Boolean = true, + clickEnabled: Boolean = false, onClick: (Expandable) -> Unit = {}, onLongClick: (Expandable) -> Unit = {}, ) { @@ -212,13 +215,12 @@ private fun LargeTileContent( ) { Box( modifier = - Modifier.fillMaxSize() - .clip(TileDefaults.TileShape) - .combinedClickable( - enabled = clickEnabled, + Modifier.fillMaxSize().clip(TileDefaults.TileShape).thenIf(clickEnabled) { + Modifier.combinedClickable( onClick = { onClick(it) }, onLongClick = { onLongClick(it) } ) + } ) { TileIcon( icon = icon, @@ -269,13 +271,29 @@ fun DefaultEditTileGrid( onAddTile: (TileSpec, Int) -> Unit, onRemoveTile: (TileSpec) -> Unit, ) { - val (currentTiles, otherTiles) = tiles.partition { it.isCurrent } - val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null } + val currentListState = rememberEditListState(tiles) + val dragAndDropState = rememberDragAndDropState(currentListState) + + val (currentTiles, otherTiles) = currentListState.tiles.partition { it.isCurrent } + val (otherTilesStock, otherTilesCustom) = + otherTiles + .filter { !dragAndDropState.isMoving(it.tileSpec) } + .partition { it.appName == null } val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState { onAddTile(it, CurrentTilesInteractor.POSITION_AT_END) } - TileLazyGrid(modifier = modifier, columns = columns) { + val onDropAdd: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, position -> + onAddTile(tileSpec, position) + } + val onDropRemove: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, _ -> + onRemoveTile(tileSpec) + } + + TileLazyGrid( + modifier = modifier.dragAndDropTileList(dragAndDropState, { true }, onDropAdd), + columns = columns + ) { // These Text are just placeholders to see the different sections. Not final UI. item(span = { GridItemSpan(maxLineSpan) }) { Text("Current tiles", color = Color.White) } @@ -285,6 +303,9 @@ fun DefaultEditTileGrid( onRemoveTile, isIconOnly, indicatePosition = true, + dragAndDropState = dragAndDropState, + acceptDrops = { true }, + onDrop = onDropAdd, ) item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) } @@ -294,6 +315,9 @@ fun DefaultEditTileGrid( ClickAction.ADD, addTileToEnd, isIconOnly, + dragAndDropState = dragAndDropState, + acceptDrops = { true }, + onDrop = onDropRemove, ) item(span = { GridItemSpan(maxLineSpan) }) { @@ -305,6 +329,9 @@ fun DefaultEditTileGrid( ClickAction.ADD, addTileToEnd, isIconOnly, + dragAndDropState = dragAndDropState, + acceptDrops = { true }, + onDrop = onDropRemove, ) } } @@ -314,6 +341,9 @@ fun LazyGridScope.editTiles( clickAction: ClickAction, onClick: (TileSpec) -> Unit, isIconOnly: (TileSpec) -> Boolean, + dragAndDropState: DragAndDropState, + acceptDrops: (TileSpec) -> Boolean, + onDrop: (TileSpec, Int) -> Unit, showLabels: Boolean = false, indicatePosition: Boolean = false, ) { @@ -322,41 +352,44 @@ fun LazyGridScope.editTiles( key = { tiles[it].tileSpec.spec }, span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) }, contentType = { TileType } - ) { - val viewModel = tiles[it] - val canClick = - when (clickAction) { - ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions - ClickAction.REMOVE -> AvailableEditActions.REMOVE in viewModel.availableEditActions - } - val onClickActionName = - when (clickAction) { - ClickAction.ADD -> - stringResource(id = R.string.accessibility_qs_edit_tile_add_action) - ClickAction.REMOVE -> - stringResource(id = R.string.accessibility_qs_edit_remove_tile_action) - } - val stateDescription = - if (indicatePosition) { - stringResource(id = R.string.accessibility_qs_edit_position, it + 1) - } else { - "" - } - + ) { index -> + val viewModel = tiles[index] val iconOnly = isIconOnly(viewModel.tileSpec) val tileHeight = tileHeight(iconOnly && showLabels) - EditTile( - tileViewModel = viewModel, - iconOnly = iconOnly, - showLabels = showLabels, - clickEnabled = canClick, - onClick = { onClick.invoke(viewModel.tileSpec) }, - modifier = - Modifier.height(tileHeight).animateItem().semantics { - onClick(onClickActionName) { false } - this.stateDescription = stateDescription + + if (!dragAndDropState.isMoving(viewModel.tileSpec)) { + val onClickActionName = + when (clickAction) { + ClickAction.ADD -> + stringResource(id = R.string.accessibility_qs_edit_tile_add_action) + ClickAction.REMOVE -> + stringResource(id = R.string.accessibility_qs_edit_remove_tile_action) } - ) + val stateDescription = + if (indicatePosition) { + stringResource(id = R.string.accessibility_qs_edit_position, index + 1) + } else { + "" + } + EditTile( + tileViewModel = viewModel, + iconOnly = iconOnly, + showLabels = showLabels, + modifier = + Modifier.height(tileHeight) + .animateItem() + .semantics { + onClick(onClickActionName) { false } + this.stateDescription = stateDescription + } + .dragAndDropTile(dragAndDropState, viewModel.tileSpec, acceptDrops, onDrop) + .dragAndDropTileSource( + viewModel.tileSpec, + onClick, + dragAndDropState, + ) + ) + } } } @@ -365,8 +398,6 @@ fun EditTile( tileViewModel: EditTileViewModel, iconOnly: Boolean, showLabels: Boolean, - clickEnabled: Boolean, - onClick: () -> Unit, modifier: Modifier = Modifier, ) { val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec @@ -377,9 +408,6 @@ fun EditTile( showLabels = showLabels, label = label, iconOnly = iconOnly, - clickEnabled = clickEnabled, - onClick = { onClick() }, - onLongClick = { onClick() }, modifier = modifier, ) { if (iconOnly) { @@ -394,9 +422,6 @@ fun EditTile( secondaryLabel = tileViewModel.appName?.load(), icon = tileViewModel.icon, colors = colors, - clickEnabled = clickEnabled, - onClick = { onClick() }, - onLongClick = { onClick() }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt index ba9a0442503d..a4c86381b785 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt @@ -26,7 +26,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec * [isCurrent] indicates whether this tile is part of the current set of tiles that the user sees in * Quick Settings. */ -class EditTileViewModel( +data class EditTileViewModel( val tileSpec: TileSpec, val icon: Icon, val label: Text, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt index 4c210804b0c0..8c75cf001441 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.base.viewmodel import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.UiBackground import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics @@ -61,6 +62,7 @@ sealed interface QSTileViewModelFactory<T> { private val qsTileConfigProvider: QSTileConfigProvider, private val systemClock: SystemClock, @Background private val backgroundDispatcher: CoroutineDispatcher, + @UiBackground private val uiBackgroundDispatcher: CoroutineDispatcher, private val customTileComponentBuilder: CustomTileComponent.Builder, ) : QSTileViewModelFactory<CustomTileDataModel> { @@ -86,6 +88,7 @@ sealed interface QSTileViewModelFactory<T> { qsTileLogger, systemClock, backgroundDispatcher, + uiBackgroundDispatcher, component.coroutineScope(), ) } @@ -106,6 +109,7 @@ sealed interface QSTileViewModelFactory<T> { private val qsTileConfigProvider: QSTileConfigProvider, private val systemClock: SystemClock, @Background private val backgroundDispatcher: CoroutineDispatcher, + @UiBackground private val uiBackgroundDispatcher: CoroutineDispatcher, private val coroutineScopeFactory: QSTileCoroutineScopeFactory, ) : QSTileViewModelFactory<T> { @@ -136,6 +140,7 @@ sealed interface QSTileViewModelFactory<T> { qsTileLogger, systemClock, backgroundDispatcher, + uiBackgroundDispatcher, coroutineScopeFactory.create(), ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt index 8782524cf250..9e84f01c6bc4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt @@ -40,6 +40,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -59,7 +60,9 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * Provides a hassle-free way to implement new tiles according to current System UI architecture @@ -81,6 +84,7 @@ class QSTileViewModelImpl<DATA_TYPE>( private val qsTileLogger: QSTileLogger, private val systemClock: SystemClock, private val backgroundDispatcher: CoroutineDispatcher, + uiBackgroundDispatcher: CoroutineDispatcher, private val tileScope: CoroutineScope, ) : QSTileViewModel, Dumpable { @@ -93,18 +97,16 @@ class QSTileViewModelImpl<DATA_TYPE>( private val tileData: SharedFlow<DATA_TYPE> = createTileDataFlow() - override val state: SharedFlow<QSTileState> = + override val state: StateFlow<QSTileState?> = tileData .map { data -> - mapper().map(config, data).also { state -> - qsTileLogger.logStateUpdate(spec, state, data) - } + withContext(uiBackgroundDispatcher) { mapper().map(config, data) } + .also { state -> qsTileLogger.logStateUpdate(spec, state, data) } } - .flowOn(backgroundDispatcher) - .shareIn( + .stateIn( tileScope, SharingStarted.WhileSubscribed(), - replay = 1, + null, ) override val isAvailable: StateFlow<Boolean> = users @@ -147,26 +149,26 @@ class QSTileViewModelImpl<DATA_TYPE>( private fun createTileDataFlow(): SharedFlow<DATA_TYPE> = users - .flatMapLatest { user -> - val updateTriggers = - merge( - userInputFlow(user), - forceUpdates - .map { DataUpdateTrigger.ForceUpdate } - .onEach { qsTileLogger.logForceUpdate(spec) }, - ) - .onStart { - emit(DataUpdateTrigger.InitialRequest) - qsTileLogger.logInitialRequest(spec) - } - .shareIn(tileScope, SharingStarted.WhileSubscribed()) - tileDataInteractor() - .tileData(user, updateTriggers) - // combine makes sure updateTriggers is always listened even if - // tileDataInteractor#tileData doesn't flatMapLatest on it - .combine(updateTriggers) { data, _ -> data } - .cancellable() - .flowOn(backgroundDispatcher) + .transformLatest { user -> + coroutineScope { + val updateTriggers: Flow<DataUpdateTrigger> = + merge( + userInputFlow(user), + forceUpdates + .map { DataUpdateTrigger.ForceUpdate } + .onEach { qsTileLogger.logForceUpdate(spec) }, + ) + .onStart { qsTileLogger.logInitialRequest(spec) } + .stateIn(this, SharingStarted.Eagerly, DataUpdateTrigger.InitialRequest) + tileDataInteractor() + .tileData(user, updateTriggers) + // combine makes sure updateTriggers is always listened even if + // tileDataInteractor#tileData doesn't transformLatest on it + .combine(updateTriggers) { data, _ -> data } + .cancellable() + .flowOn(backgroundDispatcher) + .collect { emit(it) } + } } .distinctUntilChanged() .shareIn( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt index 1e8ce588b4e0..233e913a27aa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt @@ -30,11 +30,9 @@ import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.stateIn /** Observes one qr scanner state changes providing the [QRCodeScannerTileModel]. */ class QRCodeScannerTileDataInteractor @@ -66,11 +64,6 @@ constructor( } .onStart { emit(generateModel()) } .flowOn(bgCoroutineContext) - .stateIn( - scope, - SharingStarted.WhileSubscribed(), - QRCodeScannerTileModel.TemporarilyUnavailable - ) override fun availability(user: UserHandle): Flow<Boolean> = flowOf(qrController.isCameraAvailable) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt index ae6c0143f603..30247c41200f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -54,18 +54,21 @@ data class QSTileState( resources: Resources, theme: Theme, config: QSTileUIConfig, - build: Builder.() -> Unit + builder: Builder.() -> Unit ): QSTileState { val iconDrawable = resources.getDrawable(config.iconRes, theme) return build( { Icon.Loaded(iconDrawable, null) }, resources.getString(config.labelRes), - build, + builder, ) } - fun build(icon: () -> Icon?, label: CharSequence, build: Builder.() -> Unit): QSTileState = - Builder(icon, label).apply(build).build() + fun build( + icon: () -> Icon?, + label: CharSequence, + builder: Builder.() -> Unit + ): QSTileState = Builder(icon, label).apply { builder() }.build() } enum class ActivationState(val legacyState: Int) { @@ -107,7 +110,9 @@ data class QSTileState( sealed interface SideViewIcon { data class Custom(val icon: Icon) : SideViewIcon + data object Chevron : SideViewIcon + data object None : SideViewIcon } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt index 226e2fa0549f..b1b0001b6361 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt @@ -17,7 +17,6 @@ package com.android.systemui.qs.tiles.viewmodel import android.os.UserHandle -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow /** @@ -31,7 +30,7 @@ import kotlinx.coroutines.flow.StateFlow interface QSTileViewModel { /** State of the tile to be shown by the view. */ - val state: SharedFlow<QSTileState> + val state: StateFlow<QSTileState?> val config: QSTileConfig @@ -62,9 +61,7 @@ interface QSTileViewModel { } /** - * Returns the immediate state of the tile or null if the state haven't been collected yet. Favor - * reactive consumption over the [currentState], because there is no guarantee that current value - * would be available at any time. + * Returns the immediate state of the tile or null if the state haven't been collected yet. */ val QSTileViewModel.currentState: QSTileState? - get() = state.replayCache.lastOrNull() + get() = state.value diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index 7be13e08f972..ba0a8d694a14 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -38,6 +38,7 @@ import java.util.function.Supplier import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectIndexed +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -168,6 +169,7 @@ constructor( if (listeningClients.size == 1) { stateJob = qsTileViewModel.state + .filterNotNull() .map { mapState(context, it, qsTileViewModel.config) } .onEach { legacyState -> synchronized(callbacks) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt index 64f1fd3d6416..00b7e61eb1c6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt @@ -17,12 +17,11 @@ package com.android.systemui.qs.tiles.viewmodel import android.os.UserHandle -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow object StubQSTileViewModel : QSTileViewModel { - override val state: SharedFlow<QSTileState> + override val state: StateFlow<QSTileState?> get() = error("Don't call stubs") override val config: QSTileConfig diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 23faf7d52fed..4b82e0f96560 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -62,6 +62,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; +import android.os.UserManager; import android.util.Log; import android.view.InputDevice; import android.view.KeyCharacterMap; @@ -177,7 +178,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private boolean mBound; private boolean mIsEnabled; - private boolean mIsNonPrimaryUser; + private boolean mIsSystemOrVisibleBgUser; private int mCurrentBoundedUserId = -1; private boolean mInputFocusTransferStarted; private float mInputFocusTransferStartY; @@ -629,6 +630,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis SysUiState sysUiState, Provider<SceneInteractor> sceneInteractor, UserTracker userTracker, + UserManager userManager, WakefulnessLifecycle wakefulnessLifecycle, UiEventLogger uiEventLogger, DisplayTracker displayTracker, @@ -639,10 +641,18 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder, BroadcastDispatcher broadcastDispatcher ) { - // b/241601880: This component shouldn't be running for a non-primary user - mIsNonPrimaryUser = !Process.myUserHandle().equals(UserHandle.SYSTEM); - if (mIsNonPrimaryUser) { - Log.wtf(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable()); + // b/241601880: This component should only be running for primary users or + // secondaryUsers when visibleBackgroundUsers are supported. + boolean isSystemUser = Process.myUserHandle().equals(UserHandle.SYSTEM); + boolean isVisibleBackgroundUser = + userManager.isVisibleBackgroundUsersSupported() && !userManager.isUserForeground(); + if (!isSystemUser && isVisibleBackgroundUser) { + Log.d(TAG_OPS, "Initialization for visibleBackgroundUser"); + } + mIsSystemOrVisibleBgUser = isSystemUser || isVisibleBackgroundUser; + if (!mIsSystemOrVisibleBgUser) { + Log.wtf(TAG_OPS, "Unexpected initialization for non-system foreground user", + new Throwable()); } mContext = context; @@ -833,11 +843,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } private void internalConnectToCurrentUser(String reason) { - if (mIsNonPrimaryUser) { + if (!mIsSystemOrVisibleBgUser) { // This should not happen, but if any per-user SysUI component has a dependency on OPS, // then this could get triggered - Log.w(TAG_OPS, "Skipping connection to overview service due to non-primary user " - + "caller"); + Log.w(TAG_OPS, + "Skipping connection to overview service due to non-system foreground user " + + "caller"); return; } disconnectFromLauncherService(reason); diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 25a9e9e6f9f2..ef011945b5c8 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -167,6 +167,10 @@ constructor( initialValue = isVisibleInternal() ) + /** Whether there's an ongoing remotely-initiated user interaction. */ + val isRemoteUserInteractionOngoing: StateFlow<Boolean> = + repository.isRemoteUserInteractionOngoing + /** * The amount of transition into or out of the given [scene]. * diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index d380251d8b7e..a28222e9cea0 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -26,17 +26,12 @@ import com.android.systemui.classifier.domain.interactor.FalsingInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.model.Scene import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn /** Models UI state for the scene container. */ @SysUISingleton @@ -46,7 +41,6 @@ constructor( private val sceneInteractor: SceneInteractor, private val falsingInteractor: FalsingInteractor, private val powerInteractor: PowerInteractor, - scenes: Set<@JvmSuppressWildcards Scene>, ) { /** * Keys of all scenes in the container. @@ -62,25 +56,6 @@ constructor( /** Whether the container is visible. */ val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible - private val destinationScenesBySceneKey = - scenes.associate { scene -> - scene.key to scene.destinationScenes.flatMapLatestConflated { replaceSceneFamilies(it) } - } - - fun currentDestinationScenes( - scope: CoroutineScope, - ): StateFlow<Map<UserAction, UserActionResult>> { - return currentScene - .flatMapLatestConflated { currentSceneKey -> - checkNotNull(destinationScenesBySceneKey[currentSceneKey]) - } - .stateIn( - scope = scope, - started = SharingStarted.WhileSubscribed(), - initialValue = emptyMap(), - ) - } - /** * Binds the given flow so the system remembers it. * diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java index ab4480da9e51..c4f6cd9aac29 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java @@ -23,12 +23,14 @@ import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_ import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE; import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_NAME; import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_TASK_ID; +import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CLIP_DATA; import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER; import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI; import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.PERMISSION_SELF; import android.app.Activity; import android.content.BroadcastReceiver; +import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -299,6 +301,14 @@ public class AppClipsActivity extends ComponentActivity { data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS); data.putParcelable(EXTRA_SCREENSHOT_URI, uri); + + if (mBacklinksIncludeDataCheckBox.getVisibility() == View.VISIBLE + && mBacklinksIncludeDataCheckBox.isChecked() + && mViewModel.getBacklinksLiveData().getValue() != null) { + ClipData backlinksData = mViewModel.getBacklinksLiveData().getValue().getClipData(); + data.putParcelable(EXTRA_CLIP_DATA, backlinksData); + } + try { mResultReceiver.send(Activity.RESULT_OK, data); logUiEvent(SCREENSHOT_FOR_NOTE_ACCEPTED); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java index 3c4469d052b1..0161f787b459 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java @@ -26,6 +26,7 @@ import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_ import android.app.Activity; import android.content.ActivityNotFoundException; +import android.content.ClipData; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -82,6 +83,7 @@ public class AppClipsTrampolineActivity extends Activity { private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName(); static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI"; + static final String EXTRA_CLIP_DATA = TAG + "CLIP_DATA"; static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE"; static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER"; static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME"; @@ -265,6 +267,11 @@ public class AppClipsTrampolineActivity extends Activity { convertedData.setData(uri); } + if (resultData.containsKey(EXTRA_CLIP_DATA)) { + ClipData backlinksData = resultData.getParcelable(EXTRA_CLIP_DATA, ClipData.class); + convertedData.setClipData(backlinksData); + } + // Broadcast no longer required, setting it to null. mKillAppClipsBroadcastIntent = null; diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt index 468a87325507..cebc59bdd94b 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt @@ -21,6 +21,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.core.view.setPadding import com.android.systemui.res.R import com.android.systemui.settings.brightness.BrightnessSliderController @@ -33,7 +34,15 @@ object BrightnessMirrorInflater { val frame = (LayoutInflater.from(context).inflate(R.layout.brightness_mirror_container, null) as ViewGroup) - .apply { isVisible = true } + .apply { + isVisible = true + // Match BrightnessMirrorController padding + setPadding( + context.resources.getDimensionPixelSize( + R.dimen.rounded_slider_background_padding + ) + ) + } val sliderController = sliderControllerFactory.create(context, frame) sliderController.init() frame.addView( diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt index 2651a994bb55..79e8b879288e 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.settings.brightness.ui.viewModel import android.content.res.Resources +import android.util.Log import android.view.View import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main @@ -67,25 +68,45 @@ constructor( override fun setLocationAndSize(view: View) { view.getLocationInWindow(tempPosition) val padding = resources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding) - _toggleSlider?.rootView?.setPadding(padding, padding, padding, padding) // Account for desired padding _locationAndSize.value = LocationAndSize( - yOffset = tempPosition[1] - padding, + yOffsetFromContainer = view.findTopFromContainer() - padding, + yOffsetFromWindow = tempPosition[1] - padding, width = view.measuredWidth + 2 * padding, height = view.measuredHeight + 2 * padding, ) } + private fun View.findTopFromContainer(): Int { + var out = 0 + var view = this + while (view.id != R.id.quick_settings_container) { + out += view.top + val parent = view.parent as? View + if (parent == null) { + Log.wtf(TAG, "Couldn't find container in parents of $this") + break + } + view = parent + } + return out + } + // Callbacks are used for indicating reinflation when the config changes in some ways (like // density). However, we don't need that as we recompose the view anyway override fun addCallback(listener: MirrorController.BrightnessMirrorListener) {} override fun removeCallback(listener: MirrorController.BrightnessMirrorListener) {} + + companion object { + private const val TAG = "BrightnessMirrorViewModel" + } } data class LocationAndSize( - val yOffset: Int = 0, + val yOffsetFromContainer: Int = 0, + val yOffsetFromWindow: Int = 0, val width: Int = 0, val height: Int = 0, ) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 8265cee8843e..6b4e44fed0cd 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -148,7 +148,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransition import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; -import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel; +import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; @@ -775,7 +775,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Main CoroutineDispatcher mainDispatcher, KeyguardTransitionInteractor keyguardTransitionInteractor, DumpManager dumpManager, - KeyguardLongPressViewModel keyguardLongPressViewModel, + KeyguardTouchHandlingViewModel keyguardTouchHandlingViewModel, KeyguardInteractor keyguardInteractor, ActivityStarter activityStarter, SharedNotificationContainerInteractor sharedNotificationContainerInteractor, @@ -970,7 +970,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardClockInteractor = keyguardClockInteractor; KeyguardLongPressViewBinder.bind( mView.requireViewById(R.id.keyguard_long_press), - keyguardLongPressViewModel, + keyguardTouchHandlingViewModel, () -> { onEmptySpaceClick(); return Unit.INSTANCE; @@ -1095,7 +1095,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump initBottomArea(); mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController); - mPulseExpansionHandler.setUp(mNotificationStackScrollLayoutController); mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() { @Override public void onFullyHiddenChanged(boolean isFullyHidden) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 47fd494e9e53..1c223db33fe7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -369,7 +369,9 @@ public class NotificationShadeWindowViewController implements Dumpable { } mFalsingCollector.onTouchEvent(ev); - mPulsingWakeupGestureHandler.onTouchEvent(ev); + if (!SceneContainerFlag.isEnabled()) { + mPulsingWakeupGestureHandler.onTouchEvent(ev); + } if (!SceneContainerFlag.isEnabled() && mGlanceableHubContainerController.onTouchEvent(ev)) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt index fe4832f0895b..062327dc2acf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt @@ -47,17 +47,19 @@ import javax.inject.Inject * display state, wake-ups are handled by [com.android.systemui.doze.DozeSensors]. */ @SysUISingleton -class PulsingGestureListener @Inject constructor( - private val falsingManager: FalsingManager, - private val dockManager: DockManager, - private val powerInteractor: PowerInteractor, - private val ambientDisplayConfiguration: AmbientDisplayConfiguration, - private val statusBarStateController: StatusBarStateController, - private val shadeLogger: ShadeLogger, - private val dozeInteractor: DozeInteractor, - userTracker: UserTracker, - tunerService: TunerService, - dumpManager: DumpManager +class PulsingGestureListener +@Inject +constructor( + private val falsingManager: FalsingManager, + private val dockManager: DockManager, + private val powerInteractor: PowerInteractor, + private val ambientDisplayConfiguration: AmbientDisplayConfiguration, + private val statusBarStateController: StatusBarStateController, + private val shadeLogger: ShadeLogger, + private val dozeInteractor: DozeInteractor, + userTracker: UserTracker, + tunerService: TunerService, + dumpManager: DumpManager ) : GestureDetector.SimpleOnGestureListener(), Dumpable { private var doubleTapEnabled = false private var singleTapEnabled = false @@ -66,21 +68,27 @@ class PulsingGestureListener @Inject constructor( val tunable = Tunable { key: String?, _: String? -> when (key) { Settings.Secure.DOZE_DOUBLE_TAP_GESTURE -> - doubleTapEnabled = ambientDisplayConfiguration.doubleTapGestureEnabled( - userTracker.userId) + doubleTapEnabled = + ambientDisplayConfiguration.doubleTapGestureEnabled(userTracker.userId) Settings.Secure.DOZE_TAP_SCREEN_GESTURE -> - singleTapEnabled = ambientDisplayConfiguration.tapGestureEnabled( - userTracker.userId) + singleTapEnabled = + ambientDisplayConfiguration.tapGestureEnabled(userTracker.userId) } } - tunerService.addTunable(tunable, - Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, - Settings.Secure.DOZE_TAP_SCREEN_GESTURE) + tunerService.addTunable( + tunable, + Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, + Settings.Secure.DOZE_TAP_SCREEN_GESTURE + ) dumpManager.registerDumpable(this) } override fun onSingleTapUp(e: MotionEvent): Boolean { + return onSingleTapUp(e.x, e.y) + } + + fun onSingleTapUp(x: Float, y: Float): Boolean { val isNotDocked = !dockManager.isDocked shadeLogger.logSingleTapUp(statusBarStateController.isDozing, singleTapEnabled, isNotDocked) if (statusBarStateController.isDozing && singleTapEnabled && isNotDocked) { @@ -89,11 +97,13 @@ class PulsingGestureListener @Inject constructor( shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap) if (proximityIsNotNear && isNotAFalseTap) { shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing") - dozeInteractor.setLastTapToWakePosition(Point(e.x.toInt(), e.y.toInt())) + dozeInteractor.setLastTapToWakePosition(Point(x.toInt(), y.toInt())) powerInteractor.wakeUpIfDozing("PULSING_SINGLE_TAP", PowerManager.WAKE_REASON_TAP) } + return true } + shadeLogger.d("onSingleTapUp event ignored") return false } @@ -103,10 +113,18 @@ class PulsingGestureListener @Inject constructor( * motion events for a double tap. */ override fun onDoubleTapEvent(e: MotionEvent): Boolean { + if (e.actionMasked != MotionEvent.ACTION_UP) { + return false + } + + return onDoubleTapEvent() + } + + fun onDoubleTapEvent(): Boolean { // React to the [MotionEvent.ACTION_UP] event after double tap is detected. Falsing // checks MUST be on the ACTION_UP event. - if (e.actionMasked == MotionEvent.ACTION_UP && - statusBarStateController.isDozing && + if ( + statusBarStateController.isDozing && (doubleTapEnabled || singleTapEnabled) && !falsingManager.isProximityNear && !falsingManager.isFalseDoubleTap @@ -114,6 +132,7 @@ class PulsingGestureListener @Inject constructor( powerInteractor.wakeUpIfDozing("PULSING_DOUBLE_TAP", PowerManager.WAKE_REASON_TAP) return true } + return false } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index ce321dcc03fd..5065baa04623 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -119,7 +119,7 @@ constructor( if (delayed) { scope.launch { delay(125) - animateCollapseShadeInternal() + withContext(mainDispatcher) { animateCollapseShadeInternal() } } } else { animateCollapseShadeInternal() diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt index 3f4bcba288b7..354d379d2ea2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt @@ -32,6 +32,8 @@ import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.transition.ScrimShadeTransitionController +import com.android.systemui.statusbar.PulseExpansionHandler +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.policy.SplitShadeStateController import javax.inject.Inject import javax.inject.Provider @@ -56,6 +58,8 @@ constructor( private val sceneInteractorProvider: Provider<SceneInteractor>, private val panelExpansionInteractorProvider: Provider<PanelExpansionInteractor>, private val shadeExpansionStateManager: ShadeExpansionStateManager, + private val pulseExpansionHandler: PulseExpansionHandler, + private val nsslc: NotificationStackScrollLayoutController, ) : CoreStartable { override fun start() { @@ -63,6 +67,7 @@ constructor( hydrateShadeExpansionStateManager() logTouchesTo(touchLog) scrimShadeTransitionController.init() + pulseExpansionHandler.setUp(nsslc) } private fun hydrateShadeExpansionStateManager() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index b0100b9642c2..2b2aac64a7fa 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -37,7 +37,6 @@ import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode -import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import java.util.concurrent.atomic.AtomicBoolean @@ -60,7 +59,6 @@ constructor( @Application private val applicationScope: CoroutineScope, val qsSceneAdapter: QSSceneAdapter, val shadeHeaderViewModel: ShadeHeaderViewModel, - val notifications: NotificationsPlaceholderViewModel, val brightnessMirrorViewModel: BrightnessMirrorViewModel, val mediaCarouselInteractor: MediaCarouselInteractor, shadeInteractor: ShadeInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 2f3fc729464e..c1eb8bcfa493 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -1276,7 +1276,7 @@ public class KeyguardIndicationController { mPowerPluggedInWired = status.isPluggedInWired() && isChargingOrFull; mPowerPluggedInWireless = status.isPluggedInWireless() && isChargingOrFull; mPowerPluggedInDock = status.isPluggedInDock() && isChargingOrFull; - mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull; + mPowerPluggedIn = isPowerPluggedIn(status, isChargingOrFull); mPowerCharged = status.isCharged(); mChargingWattage = status.maxChargingWattage; mChargingSpeed = status.getChargingSpeed(mContext); @@ -1562,6 +1562,11 @@ public class KeyguardIndicationController { return status.isBatteryDefender(); } + /** Return true if the device has power plugged in. */ + protected boolean isPowerPluggedIn(BatteryStatus status, boolean isChargingOrFull) { + return status.isPluggedIn() && isChargingOrFull; + } + private boolean isCurrentUser(int userId) { return getCurrentUser() == userId; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt index 933d0ab880bd..b4670320bdc0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt @@ -16,13 +16,13 @@ package com.android.systemui.statusbar.data.model -import com.android.systemui.statusbar.phone.BarTransitions -import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT -import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT -import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE -import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT -import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT -import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode +import com.android.systemui.shared.statusbar.phone.BarTransitions +import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT +import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT +import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE +import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT +import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT +import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode /** * The possible status bar modes. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index af8a89d82fe7..ef4dffad27a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -305,26 +305,20 @@ constructor( dumpManager.registerDumpable(this) } - fun isEnabled(): Boolean { - execution.assertIsMainThread() - - return plugin != null - } - - fun isDateWeatherDecoupled(): Boolean { - execution.assertIsMainThread() + val isEnabled: Boolean = plugin != null - return datePlugin != null && weatherPlugin != null - } + val isDateWeatherDecoupled: Boolean = datePlugin != null && weatherPlugin != null - fun isWeatherEnabled(): Boolean { - execution.assertIsMainThread() - val showWeather = secureSettings.getIntForUser( - LOCK_SCREEN_WEATHER_ENABLED, - 1, - userTracker.userId) == 1 - return showWeather - } + val isWeatherEnabled: Boolean + get() { + val showWeather = + secureSettings.getIntForUser( + LOCK_SCREEN_WEATHER_ENABLED, + 1, + userTracker.userId, + ) == 1 + return showWeather + } private fun updateBypassEnabled() { val bypassEnabled = bypassController.bypassEnabled @@ -337,10 +331,10 @@ constructor( fun buildAndConnectDateView(parent: ViewGroup): View? { execution.assertIsMainThread() - if (!isEnabled()) { + if (!isEnabled) { throw RuntimeException("Cannot build view when not enabled") } - if (!isDateWeatherDecoupled()) { + if (!isDateWeatherDecoupled) { throw RuntimeException("Cannot build date view when not decoupled") } @@ -361,10 +355,10 @@ constructor( fun buildAndConnectWeatherView(parent: ViewGroup): View? { execution.assertIsMainThread() - if (!isEnabled()) { + if (!isEnabled) { throw RuntimeException("Cannot build view when not enabled") } - if (!isDateWeatherDecoupled()) { + if (!isDateWeatherDecoupled) { throw RuntimeException("Cannot build weather view when not decoupled") } @@ -385,7 +379,7 @@ constructor( fun buildAndConnectView(parent: ViewGroup): View? { execution.assertIsMainThread() - if (!isEnabled()) { + if (!isEnabled) { throw RuntimeException("Cannot build view when not enabled") } @@ -577,7 +571,7 @@ constructor( } private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean { - if (isDateWeatherDecoupled() && t.featureType == SmartspaceTarget.FEATURE_WEATHER) { + if (isDateWeatherDecoupled && t.featureType == SmartspaceTarget.FEATURE_WEATHER) { return false } if (!showNotifications) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index f98f77ec4ac2..aff57bd076c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.dagger.IncomingHeader import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider import com.android.systemui.statusbar.notification.logKey +import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener @@ -726,6 +727,12 @@ class HeadsUpCoordinator @Inject constructor( */ private fun isAttemptingToShowHun(entry: ListEntry) = mHeadsUpManager.isHeadsUpEntry(entry.key) || isEntryBinding(entry) + || isHeadsUpAnimatingAway(entry) + + private fun isHeadsUpAnimatingAway(entry: ListEntry): Boolean { + if (!GroupHunAnimationFix.isEnabled) return false + return entry.representativeEntry?.row?.isHeadsUpAnimatingAway ?: false + } /** * Whether the notification is already heads up/binding per [isAttemptingToShowHun] OR if it diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt index 367aaadf2942..48c89f8fa436 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt @@ -22,19 +22,25 @@ import android.app.Notification.BubbleMetadata import android.app.Notification.CATEGORY_EVENT import android.app.Notification.CATEGORY_REMINDER import android.app.Notification.VISIBILITY_PRIVATE +import android.app.NotificationManager import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_HIGH +import android.app.PendingIntent +import android.content.Context +import android.content.Intent import android.content.pm.PackageManager import android.content.pm.PackageManager.PERMISSION_GRANTED import android.database.ContentObserver import android.hardware.display.AmbientDisplayConfiguration import android.os.Handler import android.os.PowerManager +import android.os.SystemProperties import android.provider.Settings import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED import android.provider.Settings.Global.HEADS_UP_OFF import com.android.internal.logging.UiEvent import com.android.internal.logging.UiEventLogger +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker @@ -47,6 +53,7 @@ import com.android.systemui.statusbar.notification.interruption.VisualInterrupti import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.HeadsUpManager +import com.android.systemui.util.NotificationChannels import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SystemSettings import com.android.systemui.util.time.SystemClock @@ -244,12 +251,22 @@ class AlertKeyguardVisibilitySuppressor( keyguardNotificationVisibilityProvider.shouldHideNotification(entry) } +/** + * Set with: + * adb shell setprop persist.force_show_avalanche_edu_once 1 && adb shell stop; adb shell start + */ +private const val FORCE_SHOW_AVALANCHE_EDU_ONCE = "persist.force_show_avalanche_edu_once" + +private const val PREF_HAS_SEEN_AVALANCHE_EDU = "has_seen_avalanche_edu" + class AvalancheSuppressor( private val avalancheProvider: AvalancheProvider, private val systemClock: SystemClock, private val systemSettings: SystemSettings, private val packageManager: PackageManager, private val uiEventLogger: UiEventLogger, + private val context: Context, + private val notificationManager: NotificationManager ) : VisualInterruptionFilter( types = setOf(PEEK, PULSE), @@ -257,6 +274,24 @@ class AvalancheSuppressor( ) { val TAG = "AvalancheSuppressor" + private val prefs = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE) + + // SharedPreferences are persisted across reboots + var hasSeenEdu: Boolean + get() = prefs.getBoolean(PREF_HAS_SEEN_AVALANCHE_EDU, false) + set(value) = prefs.edit().putBoolean(PREF_HAS_SEEN_AVALANCHE_EDU, value).apply() + + // Reset on reboot. + // The pipeline runs these suppressors many times very fast, so we must use a separate bool + // to force show for debug so that phone does not get stuck sending out infinite number of + // education HUNs. + private var hasShownOnceForDebug = false + + private fun shouldShowEdu() : Boolean { + val forceShowOnce = SystemProperties.get(FORCE_SHOW_AVALANCHE_EDU_ONCE, "").equals("1") + return !hasSeenEdu || (forceShowOnce && !hasShownOnceForDebug) + } + enum class State { ALLOW_CONVERSATION_AFTER_AVALANCHE, ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME, @@ -309,9 +344,46 @@ class AvalancheSuppressor( if (state != State.SUPPRESS) { return false } + if (shouldShowEdu()) { + showEdu() + } return true } + /** + * Show avalanche education HUN from SystemUI. + */ + private fun showEdu() { + val res = context.resources + val titleStr = res.getString( + com.android.systemui.res.R.string.adaptive_notification_edu_hun_title) + val textStr = res.getString( + com.android.systemui.res.R.string.adaptive_notification_edu_hun_text) + val actionStr = res.getString( + com.android.systemui.res.R.string.go_to_adaptive_notification_settings) + + val intent = Intent(Settings.ACTION_MANAGE_ADAPTIVE_NOTIFICATIONS) + val pendingIntent = PendingIntent.getActivity( + context, 0, intent, + PendingIntent.FLAG_IMMUTABLE + ) + + val builder = + Notification.Builder(context, NotificationChannels.ALERTS) + .setTicker(titleStr) + .setContentTitle(titleStr) + .setContentText(textStr) + .setSmallIcon(com.android.systemui.res.R.drawable.ic_settings) + .setCategory(Notification.CATEGORY_SYSTEM) + .setAutoCancel(true) + .addAction(android.R.drawable.button_onoff_indicator_off, actionStr, pendingIntent) + .setContentIntent(pendingIntent) + + notificationManager.notify(SystemMessage.NOTE_ADAPTIVE_NOTIFICATIONS, builder.build()) + hasSeenEdu = true + hasShownOnceForDebug = true; + } + private fun calculateState(entry: NotificationEntry): State { if ( entry.ranking.isConversation && diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt index 84f8662f5fee..96f94ca2a254 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt @@ -15,6 +15,8 @@ */ package com.android.systemui.statusbar.notification.interruption +import android.app.NotificationManager +import android.content.Context import android.content.pm.PackageManager import android.hardware.display.AmbientDisplayConfiguration import android.os.Handler @@ -68,7 +70,9 @@ constructor( private val avalancheProvider: AvalancheProvider, private val systemSettings: SystemSettings, private val packageManager: PackageManager, - private val bubbles: Optional<Bubbles> + private val bubbles: Optional<Bubbles>, + private val context: Context, + private val notificationManager: NotificationManager ) : VisualInterruptionDecisionProvider { init { @@ -179,7 +183,7 @@ constructor( if (NotificationAvalancheSuppression.isEnabled) { addFilter( AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, - uiEventLogger) + uiEventLogger, context, notificationManager) ) avalancheProvider.register() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index f73223f3370b..4a043d3617c3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2869,14 +2869,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public boolean isExpanded(boolean allowOnKeyguard) { - // System expanded should be ignored in heads up state final boolean isHeadsUpState = ExpandHeadsUpOnInlineReply.isEnabled() && canShowHeadsUp() && isHeadsUpState(); + // System expanded should be ignored in pinned heads up state + final boolean isPinned = isHeadsUpState && isPinned(); // Heads Up Notification can be expanded when it is pinned. final boolean isPinnedAndExpanded = isHeadsUpState && isPinnedAndExpanded(); + return (!shouldShowPublic()) && (!mOnKeyguard || allowOnKeyguard) - && (!hasUserChangedExpansion() && !isHeadsUpState + && (!hasUserChangedExpansion() && !isPinned && (isSystemExpanded() || isSystemChildExpanded()) || isUserExpanded() || isPinnedAndExpanded); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 96b1cf2db662..646d0b11221b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1477,7 +1477,7 @@ public class NotificationContentView extends FrameLayout implements Notification } if (hasRemoteInput) { result.mView.setWrapper(wrapper); - result.mView.addOnVisibilityChangedListener(this::setRemoteInputVisible); + result.mView.setOnVisibilityChangedListener(this::setRemoteInputVisible); if (existingPendingIntent != null || result.mView.isActive()) { // The current action could be gone, or the pending intent no longer valid. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/GroupHunAnimationFix.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/GroupHunAnimationFix.kt new file mode 100644 index 000000000000..5867612d0b51 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/GroupHunAnimationFix.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for com.android.systemui.Flags.FLAG_NOTIFICATION_GROUP_HUN_REMOVAL_ANIMATION_FIX */ +@Suppress("NOTHING_TO_INLINE") +object GroupHunAnimationFix { + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_GROUP_HUN_REMOVAL_ANIMATION_FIX + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Are sections sorted by time? */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationGroupHunRemovalAnimationFix() + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an eng + * build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception if + * the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index ddfa86d1e26f..715c6e65d1de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -1500,6 +1500,7 @@ public class NotificationStackScrollLayout * needed. */ void setOnStackYChanged(Consumer<Boolean> onStackYChanged) { + SceneContainerFlag.assertInLegacyMode(); mOnStackYChanged = onStackYChanged; } @@ -2270,6 +2271,7 @@ public class NotificationStackScrollLayout public void setOverscrollTopChangedListener( OnOverscrollTopChangedListener overscrollTopChangedListener) { + SceneContainerFlag.assertInLegacyMode(); mOverscrollTopChangedListener = overscrollTopChangedListener; } @@ -5705,6 +5707,7 @@ public class NotificationStackScrollLayout * Set a listener to when scrolling changes. */ public void setOnScrollListener(Consumer<Integer> listener) { + SceneContainerFlag.assertInLegacyMode(); mScrollListener = listener; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index bf53ee2b73c9..12f8f6996cb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1052,6 +1052,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void setOverscrollTopChangedListener( OnOverscrollTopChangedListener listener) { + SceneContainerFlag.assertInLegacyMode(); mView.setOverscrollTopChangedListener(listener); } @@ -1248,6 +1249,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { } public void setOnStackYChanged(Consumer<Boolean> onStackYChanged) { + SceneContainerFlag.assertInLegacyMode(); mView.setOnStackYChanged(onStackYChanged); } @@ -1750,6 +1752,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { * Set a listener to when scrolling changes. */ public void setOnScrollListener(Consumer<Integer> listener) { + SceneContainerFlag.assertInLegacyMode(); mView.setOnScrollListener(listener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index ebb0d7dcee10..57e52b7dc2ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -70,10 +70,10 @@ constructor( ) { shadeExpansion, shadeMode, qsExpansion, transitionState, quickSettingsScene -> when (transitionState) { is ObservableTransitionState.Idle -> { - if (transitionState.currentScene == Scenes.Lockscreen) { - 1f - } else { - shadeExpansion + when (transitionState.currentScene) { + Scenes.Lockscreen, + Scenes.QuickSettings -> 1f + else -> shadeExpansion } } is ObservableTransitionState.Transition -> { @@ -162,9 +162,13 @@ constructor( stackAppearanceInteractor::setCurrentGestureOverscroll /** Whether the notification stack is scrollable or not. */ - val isScrollable: Flow<Boolean> = sceneInteractor.currentScene.map { - sceneInteractor.isSceneInFamily(it, SceneFamilies.NotifShade) || it == Scenes.Lockscreen - }.dumpWhileCollecting("isScrollable") + val isScrollable: Flow<Boolean> = + sceneInteractor.currentScene + .map { + sceneInteractor.isSceneInFamily(it, SceneFamilies.NotifShade) || + it == Scenes.Lockscreen + } + .dumpWhileCollecting("isScrollable") /** Whether the notification stack is displayed in doze mode. */ val isDozing: Flow<Boolean> by lazy { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 634bd7e4cd41..08e81d556216 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -20,15 +20,16 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding import com.android.systemui.util.kotlin.FlowDumperImpl import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow /** * ViewModel used by the Notification placeholders inside the scene container to update the @@ -41,8 +42,8 @@ constructor( dumpManager: DumpManager, private val interactor: NotificationStackAppearanceInteractor, shadeInteractor: ShadeInteractor, + private val shadeSceneViewModel: ShadeSceneViewModel, featureFlags: FeatureFlagsClassic, - private val keyguardInteractor: KeyguardInteractor, ) : FlowDumperImpl(dumpManager) { /** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */ val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES) @@ -60,11 +61,19 @@ constructor( interactor.setConstrainedAvailableSpace(height) } + /** Notifies that empty space on the notification scrim has been clicked. */ + fun onEmptySpaceClicked() { + shadeSceneViewModel.onContentClicked() + } + /** Sets the content alpha for the current state of the brightness mirror */ fun setAlphaForBrightnessMirror(alpha: Float) { interactor.setAlphaForBrightnessMirror(alpha) } + /** Whether or not the notification scrim should be clickable. */ + val isClickable: StateFlow<Boolean> = shadeSceneViewModel.isClickable + /** Corner rounding of the stack */ val shadeScrimRounding: Flow<ShadeScrimRounding> = interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index fae0a4681493..97266c57ba09 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -126,6 +126,7 @@ constructor( animationController: ActivityTransitionAnimator.Controller?, fillInIntent: Intent?, extraOptions: Bundle?, + customMessage: String?, ) { activityStarterInternal.startPendingIntentDismissingKeyguard( intent = intent, @@ -135,6 +136,7 @@ constructor( dismissShade = dismissShade, fillInIntent = fillInIntent, extraOptions = extraOptions, + customMessage = customMessage, ) } @@ -319,11 +321,13 @@ constructor( intent: Intent, onlyProvisioned: Boolean, dismissShade: Boolean, + customMessage: String?, ) { activityStarterInternal.startActivityDismissingKeyguard( intent = intent, onlyProvisioned = onlyProvisioned, dismissShade = dismissShade, + customMessage = customMessage, ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt index cff9f5edad08..93ce6e8561ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt @@ -42,6 +42,7 @@ interface ActivityStarterInternal { skipLockscreenChecks: Boolean = false, fillInIntent: Intent? = null, extraOptions: Bundle? = null, + customMessage: String? = null, ) /** Starts an activity after dismissing keyguard. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt index dbb95e602e43..ae98e1d60589 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt @@ -42,7 +42,8 @@ class ActivityStarterInternalImpl @Inject constructor() : ActivityStarterInterna showOverLockscreen: Boolean, skipLockscreenChecks: Boolean, fillInIntent: Intent?, - extraOptions: Bundle? + extraOptions: Bundle?, + customMessage: String?, ) { TODO("Not yet implemented b/308819693") } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index d75a738898b3..0a02381da4d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -44,6 +44,7 @@ import com.android.systemui.display.data.repository.DisplayMetricsRepository; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.qs.QSPanelController; +import com.android.systemui.shared.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.util.Compile; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 5c262f3124a9..491db307ae63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -181,6 +181,7 @@ import com.android.systemui.shade.ShadeLogger; import com.android.systemui.shade.ShadeSurface; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shared.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CircleReveal; import com.android.systemui.statusbar.CommandQueue; @@ -1328,7 +1329,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { .putExtra(Intent.EXTRA_TEXT, message.toString()), "Share rejected touch report") .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), - true /* onlyProvisioned */, true /* dismissShade */); + true /* onlyProvisioned */, + true /* dismissShade */, + null /* customMessage */); }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index c5dcb0933407..4ce901030e57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -31,6 +31,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; @@ -105,6 +106,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements private boolean mIsExpanded; private int mStatusBarState; private AnimationStateHandler mAnimationStateHandler; + + private Handler mBgHandler; private int mHeadsUpInset; // Used for determining the region for touch interaction @@ -149,7 +152,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements UiEventLogger uiEventLogger, JavaAdapter javaAdapter, ShadeInteractor shadeInteractor, - AvalancheController avalancheController) { + AvalancheController avalancheController, + @Background Handler bgHandler) { super(context, logger, handler, globalSettings, systemClock, executor, accessibilityManagerWrapper, uiEventLogger, avalancheController); Resources resources = mContext.getResources(); @@ -159,7 +163,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements mGroupMembershipManager = groupMembershipManager; mVisualStabilityProvider = visualStabilityProvider; mAvalancheController = avalancheController; - + mBgHandler = bgHandler; updateResources(); configurationController.addCallback(new ConfigurationController.ConfigurationListener() { @Override @@ -401,7 +405,11 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements // Waiting HUNs in AvalancheController are still promoted to the HUN section and thus // seen in open shade; clear them so we don't show them again when the shade closes and // reordering is allowed again. - mAvalancheController.logDroppedHuns(mAvalancheController.getWaitingKeys().size()); + int waitingKeysSize = mAvalancheController.getWaitingKeys().size(); + mBgHandler.post(() -> { + // Do this in the background to avoid missing frames when closing the shade + mAvalancheController.logDroppedHuns(waitingKeysSize); + }); mAvalancheController.clearNext(); // In open shade the first HUN is pinned, and visual stability logic prevents us from diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt index 8a45ec15b627..4aece3d5cd6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt @@ -42,7 +42,7 @@ class KeyguardBottomAreaViewController } override fun onViewAttached() { - if (!smartspaceRelocateToBottom() || !smartspaceController.isEnabled()) { + if (!smartspaceRelocateToBottom() || !smartspaceController.isEnabled) { return } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt index e96326a945b4..bcb613fe2b8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt @@ -232,6 +232,7 @@ constructor( skipLockscreenChecks: Boolean, fillInIntent: Intent?, extraOptions: Bundle?, + customMessage: String?, ) { val animationController = if (associatedView is ExpandableNotificationRow) { @@ -340,6 +341,7 @@ constructor( afterKeyguardGone = willLaunchResolverActivity, dismissShade = collapse, willAnimateOnKeyguard = animate, + customMessage = customMessage, ) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index eec617bf91d3..a33996b99900 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -19,8 +19,8 @@ package com.android.systemui.statusbar.phone; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT; import android.content.Context; import android.graphics.Rect; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java index ae3f92355510..6676a7f7cdf8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java @@ -23,6 +23,7 @@ import android.content.res.Resources; import android.view.View; import com.android.systemui.res.R; +import com.android.systemui.shared.statusbar.phone.BarTransitions; public final class PhoneStatusBarTransitions extends BarTransitions { private static final float ICON_ALPHA_WHEN_NOT_OPAQUE = 1; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java index 29c13723ca89..25b8bfe0da25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSLUCENT; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_WARNING; import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_VIEW; import android.annotation.NonNull; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index 02187844d6da..45aee5b8c22f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -29,6 +29,7 @@ import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; import android.view.WindowInsets; +import androidx.annotation.VisibleForTesting; import com.android.compose.animation.scene.ObservableTransitionState; import com.android.internal.policy.SystemBarUtils; import com.android.systemui.Dumpable; @@ -69,6 +70,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { private boolean mIsStatusBarExpanded = false; private boolean mIsIdleOnGone = true; + private boolean mIsRemoteUserInteractionOngoing = false; private boolean mShouldAdjustInsets = false; private View mNotificationShadeWindowView; private View mNotificationPanelView; @@ -133,6 +135,9 @@ public final class StatusBarTouchableRegionManager implements Dumpable { javaAdapter.alwaysCollectFlow( sceneInteractor.get().getTransitionState(), this::onSceneChanged); + javaAdapter.alwaysCollectFlow( + sceneInteractor.get().isRemoteUserInteractionOngoing(), + this::onRemoteUserInteractionOngoingChanged); } else { javaAdapter.alwaysCollectFlow( shadeInteractor.isAnyExpanded(), @@ -179,6 +184,13 @@ public final class StatusBarTouchableRegionManager implements Dumpable { } } + private void onRemoteUserInteractionOngoingChanged(Boolean ongoing) { + if (ongoing != mIsRemoteUserInteractionOngoing) { + mIsRemoteUserInteractionOngoing = ongoing; + updateTouchableRegion(); + } + } + /** * Calculates the touch region needed for heads up notifications, taking into consideration * any existing display cutouts (notch) @@ -276,13 +288,15 @@ public final class StatusBarTouchableRegionManager implements Dumpable { * Helper to let us know when calculating the region is not needed because we know the entire * screen needs to be touchable. */ - private boolean shouldMakeEntireScreenTouchable() { + @VisibleForTesting + boolean shouldMakeEntireScreenTouchable() { // The touchable region is always the full area when expanded, whether we're showing the // shade or the bouncer. It's also fully touchable when the screen off animation is playing // since we don't want stray touches to go through the light reveal scrim to whatever is // underneath. return mIsStatusBarExpanded - || (SceneContainerFlag.isEnabled() && !mIsIdleOnGone) + || (SceneContainerFlag.isEnabled() + && (!mIsIdleOnGone || mIsRemoteUserInteractionOngoing)) || mPrimaryBouncerInteractor.isShowing().getValue() || mAlternateBouncerInteractor.isVisibleState() || mUnlockedScreenOffAnimationController.isAnimationPlaying(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 3d8090d70faa..aced0be4cc46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -14,6 +14,8 @@ package com.android.systemui.statusbar.phone.fragment; +import static com.android.systemui.statusbar.phone.fragment.StatusBarVisibilityModel.createHiddenModel; + import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.Fragment; @@ -46,6 +48,7 @@ import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.CommandQueue; @@ -205,6 +208,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private boolean mTransitionFromLockscreenToDreamStarted = false; /** + * True if the current scene allows the home status bar (aka this status bar) to be shown, and + * false if the current scene should never show the home status bar. Only used if the scene + * container is enabled. + */ + private boolean mHomeStatusBarAllowedByScene = true; + + /** * True if there's an active ongoing activity that should be showing a chip and false otherwise. */ private boolean mHasOngoingActivity; @@ -522,6 +532,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mHasOngoingActivity = hasOngoingActivity; updateStatusBarVisibilities(/* animate= */ true); } + + @Override + public void onIsHomeStatusBarAllowedBySceneChanged( + boolean isHomeStatusBarAllowedByScene) { + mHomeStatusBarAllowedByScene = isHomeStatusBarAllowedByScene; + updateStatusBarVisibilities(/* animate= */ true); + } }; @Override @@ -580,17 +597,22 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue boolean headsUpVisible = mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible(); - if (!mKeyguardStateController.isLaunchTransitionFadingAway() - && !mKeyguardStateController.isKeyguardFadingAway() - && shouldHideStatusBar() - && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD - && headsUpVisible)) { - // Hide everything - return new StatusBarVisibilityModel( - /* showClock= */ false, - /* showNotificationIcons= */ false, - /* showOngoingActivityChip= */ false, - /* showSystemInfo= */ false); + if (SceneContainerFlag.isEnabled()) { + // With the scene container, only use the value calculated by the view model to + // determine if the status bar needs hiding. + if (!mHomeStatusBarAllowedByScene) { + return createHiddenModel(); + } + } else { + // Without the scene container, use our old, mildly-hacky logic to determine if the + // status bar needs hiding. + if (!mKeyguardStateController.isLaunchTransitionFadingAway() + && !mKeyguardStateController.isKeyguardFadingAway() + && shouldHideStatusBar() + && !(mStatusBarStateController.getState() == StatusBarState.KEYGUARD + && headsUpVisible)) { + return createHiddenModel(); + } } boolean showClock = externalModel.getShowClock() && !headsUpVisible; @@ -698,7 +720,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void hideEndSideContent(boolean animate) { - if (!animate) { + if (!animate || !mAnimationsEnabled) { mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER); } else { mEndSideAlphaController.animateToAlpha(/*alpha*/ 0f, SOURCE_OTHER, FADE_OUT_DURATION, @@ -707,7 +729,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void showEndSideContent(boolean animate) { - if (!animate) { + if (!animate || !mAnimationsEnabled) { mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER); return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt index fe24faece1d3..9255e6323d31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarVisibilityModel.kt @@ -36,6 +36,17 @@ data class StatusBarVisibilityModel( return createModelFromFlags(DISABLE_NONE, DISABLE2_NONE) } + /** Creates a model that hides every piece of the status bar. */ + @JvmStatic + fun createHiddenModel(): StatusBarVisibilityModel { + return StatusBarVisibilityModel( + showClock = false, + showNotificationIcons = false, + showOngoingActivityChip = false, + showSystemInfo = false, + ) + } + /** * Given a set of disabled flags, converts them into the correct visibility statuses. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt index de36e407da84..ae1898bc479c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt @@ -30,6 +30,7 @@ import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.statusbar.chips.ui.binder.ChipChronometerBinder import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer @@ -136,6 +137,14 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa } } } + + if (SceneContainerFlag.isEnabled) { + launch { + viewModel.isHomeStatusBarAllowedByScene.collect { + listener.onIsHomeStatusBarAllowedBySceneChanged(it) + } + } + } } } } @@ -259,4 +268,10 @@ interface StatusBarVisibilityChangeListener { /** Called when the status of the ongoing activity chip (active or not active) has changed. */ fun onOngoingActivityStatusChanged(hasOngoingActivity: Boolean) + + /** + * Called when the scene state has changed such that the home status bar is newly allowed or no + * longer allowed. See [CollapsedStatusBarViewModel.isHomeStatusBarAllowedByScene]. + */ + fun onIsHomeStatusBarAllowedBySceneChanged(isHomeStatusBarAllowedByScene: Boolean) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt index 95d924889301..d6c3834c1bf1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt @@ -24,6 +24,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModel import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor @@ -65,6 +68,12 @@ interface CollapsedStatusBarViewModel { val ongoingActivityChip: StateFlow<OngoingActivityChipModel> /** + * True if the current scene can show the home status bar (aka this status bar), and false if + * the current scene should never show the home status bar. + */ + val isHomeStatusBarAllowedByScene: StateFlow<Boolean> + + /** * Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where * status bar and navigation icons dim. In this mode, a notification dot appears where the * notification icons would appear if they would be shown outside of this mode. @@ -83,6 +92,8 @@ constructor( private val lightsOutInteractor: LightsOutInteractor, private val notificationsInteractor: ActiveNotificationsInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, + sceneInteractor: SceneInteractor, + sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor, ongoingActivityChipsViewModel: OngoingActivityChipsViewModel, @Application coroutineScope: CoroutineScope, ) : CollapsedStatusBarViewModel { @@ -99,6 +110,20 @@ constructor( override val ongoingActivityChip = ongoingActivityChipsViewModel.chip + override val isHomeStatusBarAllowedByScene: StateFlow<Boolean> = + combine( + sceneInteractor.currentScene, + sceneContainerOcclusionInteractor.invisibleDueToOcclusion, + ) { currentScene, isOccluded -> + // All scenes have their own status bars, so we should only show the home status bar + // if we're not in a scene. The one exception: If the scene is occluded, then the + // occluding app needs to show the status bar. (Fullscreen apps actually won't show + // the status bar but that's handled with the rest of our fullscreen app logic, + // which lives elsewhere.) + currentScene == Scenes.Gone || isOccluded + } + .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false) + override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = if (NotificationsLiveDataStoreRefactor.isUnexpectedlyInLegacyMode()) { emptyFlow() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index fad5df827ffa..220e729625af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -834,6 +834,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * @return true if the notification is sticky */ public boolean isSticky() { + if (mEntry == null) return false; return (mEntry.isRowPinned() && mExpanded) || mRemoteInputActive || hasFullScreenIntent(mEntry); 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 1fc7bf467757..31776cf5ad1b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -115,7 +115,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private final SendButtonTextWatcher mTextWatcher; private final TextView.OnEditorActionListener mEditorActionHandler; private final ArrayList<Runnable> mOnSendListeners = new ArrayList<>(); - private final ArrayList<Consumer<Boolean>> mOnVisibilityChangedListeners = new ArrayList<>(); + private Consumer<Boolean> mOnVisibilityChangedListener = null; private final ArrayList<OnFocusChangeListener> mEditTextFocusChangeListeners = new ArrayList<>(); @@ -733,24 +733,17 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene * {@link #getVisibility()} would return {@link View#VISIBLE}, and {@code false} it would return * any other value. */ - public void addOnVisibilityChangedListener(Consumer<Boolean> listener) { - mOnVisibilityChangedListeners.add(listener); - } - - /** - * Unregister a listener previously registered via - * {@link #addOnVisibilityChangedListener(Consumer)}. - */ - public void removeOnVisibilityChangedListener(Consumer<Boolean> listener) { - mOnVisibilityChangedListeners.remove(listener); + public void setOnVisibilityChangedListener(Consumer<Boolean> listener) { + mOnVisibilityChangedListener = listener; } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (changedView == this) { - for (Consumer<Boolean> listener : new ArrayList<>(mOnVisibilityChangedListeners)) { - listener.accept(visibility == VISIBLE); + final Consumer<Boolean> visibilityChangedListener = mOnVisibilityChangedListener; + if (visibilityChangedListener != null) { + visibilityChangedListener.accept(visibility == VISIBLE); } // Hide soft-keyboard when the input view became invisible // (i.e. The notification shade collapsed by pressing the home key) diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt index d10554f9c254..b836016eb2ef 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt @@ -20,13 +20,16 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Tracing +import com.android.systemui.dagger.qualifiers.UiBackground import dagger.Module import dagger.Provides +import java.util.concurrent.Executor import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.newFixedThreadPoolContext import kotlinx.coroutines.plus @@ -83,4 +86,25 @@ class SysUICoroutinesModule { ): CoroutineContext { return bgCoroutineDispatcher + tracingCoroutineContext } + + /** Coroutine dispatcher for background operations on for UI. */ + @Provides + @SysUISingleton + @UiBackground + @Deprecated( + "Use @UiBackground CoroutineContext instead", + ReplaceWith("uiBgCoroutineContext()", "kotlin.coroutines.CoroutineContext") + ) + fun uiBgDispatcher(@UiBackground uiBgExecutor: Executor): CoroutineDispatcher = + uiBgExecutor.asCoroutineDispatcher() + + @Provides + @UiBackground + @SysUISingleton + fun uiBgCoroutineContext( + @Tracing tracingCoroutineContext: CoroutineContext, + @UiBackground uiBgCoroutineDispatcher: CoroutineDispatcher, + ): CoroutineContext { + return uiBgCoroutineDispatcher + tracingCoroutineContext + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt index 1ae5614ae4b6..2e7b05a5422f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt @@ -32,6 +32,7 @@ import com.android.settingslib.volume.shared.AudioManagerEventsReceiverImpl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.volume.shared.VolumeLogger import dagger.Module import dagger.Provides import kotlin.coroutines.CoroutineContext @@ -58,6 +59,7 @@ interface AudioModule { contentResolver: ContentResolver, @Background coroutineContext: CoroutineContext, @Application coroutineScope: CoroutineScope, + volumeLogger: VolumeLogger, ): AudioRepository = AudioRepositoryImpl( intentsReceiver, @@ -65,6 +67,7 @@ interface AudioModule { contentResolver, coroutineContext, coroutineScope, + volumeLogger, ) @Provides diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt index c18573ed1545..521f608878cb 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt @@ -26,6 +26,7 @@ import com.android.settingslib.volume.shared.model.AudioStreamModel import com.android.settingslib.volume.shared.model.RingerMode import com.android.systemui.common.shared.model.Icon import com.android.systemui.res.R +import com.android.systemui.volume.panel.shared.VolumePanelLogger import com.android.systemui.volume.panel.ui.VolumePanelUiEvent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -51,6 +52,7 @@ constructor( private val context: Context, private val audioVolumeInteractor: AudioVolumeInteractor, private val uiEventLogger: UiEventLogger, + private val volumePanelLogger: VolumePanelLogger, ) : SliderViewModel { private val volumeChanges = MutableStateFlow<Int?>(null) @@ -105,6 +107,7 @@ constructor( audioVolumeInteractor.canChangeVolume(audioStream), audioVolumeInteractor.ringerMode, ) { model, isEnabled, ringerMode -> + volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume) model.toState(isEnabled, ringerMode) } .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty) @@ -112,7 +115,10 @@ constructor( init { volumeChanges .filterNotNull() - .onEach { audioVolumeInteractor.setVolume(audioStream, it) } + .onEach { + volumePanelLogger.onSetVolumeRequested(audioStream, it) + audioVolumeInteractor.setVolume(audioStream, it) + } .launchIn(coroutineScope) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt new file mode 100644 index 000000000000..cc513b5d820c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.shared + +import com.android.settingslib.volume.shared.model.AudioStream +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.VolumeLog +import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope +import javax.inject.Inject + +private const val TAG = "SysUI_VolumePanel" + +/** Logs events related to the Volume Panel. */ +@VolumePanelScope +class VolumePanelLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) { + + fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = audioStream.toString() + int1 = volume + }, + { "Set volume: stream=$str1 volume=$int1" } + ) + } + + fun onVolumeUpdateReceived(audioStream: AudioStream, volume: Int) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = audioStream.toString() + int1 = volume + }, + { "Volume update received: stream=$str1 volume=$int1" } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt new file mode 100644 index 000000000000..869a82a78848 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.shared + +import com.android.settingslib.volume.data.repository.AudioRepositoryImpl +import com.android.settingslib.volume.shared.model.AudioStream +import com.android.settingslib.volume.shared.model.AudioStreamModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.VolumeLog +import javax.inject.Inject + +private const val TAG = "SysUI_Volume" + +/** Logs general System UI volume events. */ +@SysUISingleton +class VolumeLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) : + AudioRepositoryImpl.Logger { + + override fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = audioStream.toString() + int1 = volume + }, + { "Set volume: stream=$str1 volume=$int1" } + ) + } + + override fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = audioStream.toString() + int1 = model.volume + }, + { "Volume update received: stream=$str1 volume=$int1" } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index cb61534b2255..8457bdb2d0ff 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -51,7 +51,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.flags.FeatureFlags; diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index ec9b5cfbdeb2..3f1ec85ae99a 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -20,8 +20,9 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_B import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; @@ -66,6 +67,7 @@ import com.android.wm.shell.onehanded.OneHandedEventCallback; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.onehanded.OneHandedUiEventLogger; import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.sysui.ShellInterface; @@ -249,7 +251,25 @@ public final class WMShell implements pip.showPictureInPictureMenu(); } }); + pip.registerPipTransitionCallback( + new PipTransitionController.PipTransitionCallback() { + @Override + public void onPipTransitionStarted(int direction, Rect pipBounds) { + mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, true) + .commitUpdate(mDisplayTracker.getDefaultDisplayId()); + } + + @Override + public void onPipTransitionFinished(int direction) { + mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false) + .commitUpdate(mDisplayTracker.getDefaultDisplayId()); + } + @Override + public void onPipTransitionCanceled(int direction) { + // No op. + } + }, mSysUiMainExecutor); mSysUiState.addCallback(sysUiStateFlag -> { mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0; pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 0ed40e9be471..97f5efcf4c5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.biometrics.ui.kosmos.promptViewmodel +package com.android.systemui.biometrics.ui.viewmodel import android.app.ActivityManager.RunningTaskInfo import android.content.ComponentName @@ -66,12 +66,6 @@ import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.biometrics.shared.model.toSensorStrength import com.android.systemui.biometrics.shared.model.toSensorType import com.android.systemui.biometrics.udfpsUtils -import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode -import com.android.systemui.biometrics.ui.viewmodel.PromptMessage -import com.android.systemui.biometrics.ui.viewmodel.PromptPosition -import com.android.systemui.biometrics.ui.viewmodel.PromptSize -import com.android.systemui.biometrics.ui.viewmodel.iconProvider -import com.android.systemui.biometrics.ui.viewmodel.promptViewModel import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java index ddf69b5b964c..c2173c43ad45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java @@ -132,7 +132,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor, false); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setHaveFavorites(false); @@ -145,7 +145,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor, false); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setHaveFavorites(false); @@ -158,7 +158,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor, false); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setHaveFavorites(false); @@ -171,7 +171,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor, false); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setHaveFavorites(true); @@ -184,7 +184,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor, false); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setHaveFavorites(true); @@ -197,7 +197,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor, false); + mDreamOverlayStateController, mControlsComponent, mMonitor); registrant.start(); setServiceAvailable(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt new file mode 100644 index 000000000000..6985439f0552 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.data.repository + +import android.view.KeyEvent +import android.view.KeyboardShortcutGroup +import android.view.KeyboardShortcutInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyboard.shortcut.shared.model.Shortcut +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand +import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory +import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository +import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() { + @OptIn(ExperimentalCoroutinesApi::class) + private val kosmos = testKosmos().also { it.testDispatcher = UnconfinedTestDispatcher() } + private val repo = kosmos.shortcutHelperCategoriesRepository + private val helper = kosmos.shortcutHelperTestHelper + private val testScope = kosmos.testScope + + @Test + fun stateActive_imeShortcuts_shortcutInfoCorrectlyConverted() = + testScope.runTest { + helper.setImeShortcuts(imeShortcutsGroupWithPreviousLanguageSwitchShortcut) + val imeShortcutCategory by collectLastValue(repo.imeShortcutsCategory) + + helper.showFromActivity() + + assertThat(imeShortcutCategory) + .isEqualTo(expectedImeShortcutCategoryWithPreviousLanguageSwitchShortcut) + } + + @Test + fun stateActive_imeShortcuts_discardUnsupportedShortcutInfoModifiers() = + testScope.runTest { + helper.setImeShortcuts(imeShortcutsGroupWithUnsupportedShortcutModifiers) + val imeShortcutCategory by collectLastValue(repo.imeShortcutsCategory) + + helper.showFromActivity() + + assertThat(imeShortcutCategory) + .isEqualTo(expectedImeShortcutCategoryWithDiscardedUnsupportedShortcuts) + } + + private val switchToPreviousLanguageCommand = + ShortcutCommand( + listOf(KeyEvent.META_CTRL_ON, KeyEvent.META_SHIFT_ON, KeyEvent.KEYCODE_SPACE) + ) + + private val expectedImeShortcutCategoryWithDiscardedUnsupportedShortcuts = + shortcutCategory(ShortcutCategoryType.IME) { subCategory("input", emptyList()) } + + private val switchToPreviousLanguageKeyboardShortcutInfo = + KeyboardShortcutInfo( + /* label = */ "switch to previous language", + /* keycode = */ switchToPreviousLanguageCommand.keyCodes[2], + /* modifiers = */ switchToPreviousLanguageCommand.keyCodes[0] or + switchToPreviousLanguageCommand.keyCodes[1], + ) + + private val expectedImeShortcutCategoryWithPreviousLanguageSwitchShortcut = + shortcutCategory(ShortcutCategoryType.IME) { + subCategory( + "input", + listOf( + Shortcut( + switchToPreviousLanguageKeyboardShortcutInfo.label!!.toString(), + listOf(switchToPreviousLanguageCommand) + ) + ) + ) + } + + private val imeShortcutsGroupWithPreviousLanguageSwitchShortcut = + listOf( + KeyboardShortcutGroup( + "input", + listOf( + switchToPreviousLanguageKeyboardShortcutInfo, + ) + ) + ) + + private val shortcutInfoWithUnsupportedModifier = + KeyboardShortcutInfo( + /* label = */ "unsupported shortcut", + /* keycode = */ KeyEvent.KEYCODE_SPACE, + /* modifiers = */ 32 + ) + + private val imeShortcutsGroupWithUnsupportedShortcutModifiers = + listOf( + KeyboardShortcutGroup( + "input", + listOf( + shortcutInfoWithUnsupportedModifier, + ) + ) + ) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt index 9c9e48e9200e..5c7ce3ef1009 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt @@ -16,10 +16,17 @@ package com.android.systemui.keyboard.shortcut.domain.interactor +import android.view.KeyEvent +import android.view.KeyboardShortcutGroup +import android.view.KeyboardShortcutInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory +import com.android.systemui.keyboard.shortcut.shared.model.shortcut import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource @@ -57,6 +64,7 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { @Test fun categories_stateActive_emitsAllCategoriesInOrder() = testScope.runTest { + helper.setImeShortcuts(imeShortcutGroups) val categories by collectLastValue(interactor.shortcutCategories) helper.showFromActivity() @@ -64,7 +72,8 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { assertThat(categories) .containsExactly( systemShortcutsSource.systemShortcutsCategory(), - multitaskingShortcutsSource.multitaskingShortcutCategory() + multitaskingShortcutsSource.multitaskingShortcutCategory(), + imeShortcutCategory ) .inOrder() } @@ -78,4 +87,165 @@ class ShortcutHelperCategoriesInteractorTest : SysuiTestCase() { assertThat(categories).isEmpty() } + + fun categories_stateActive_emitsGroupedShortcuts() = + testScope.runTest { + helper.setImeShortcuts(imeShortcutsGroupsWithDuplicateLabels) + val categories by collectLastValue(interactor.shortcutCategories) + + helper.showFromActivity() + + assertThat(categories) + .containsExactly( + systemShortcutsSource.systemShortcutsCategory(), + multitaskingShortcutsSource.multitaskingShortcutCategory(), + expectedGroupedShortcutCategories + ) + } + + private val switchToNextLanguageShortcut = + shortcut(label = "switch to next language") { + command(KeyEvent.META_CTRL_ON, KeyEvent.KEYCODE_SPACE) + } + + private val switchToNextLanguageKeyboardShortcutInfo = + KeyboardShortcutInfo( + /* label = */ switchToNextLanguageShortcut.label, + /* keycode = */ switchToNextLanguageShortcut.commands[0].keyCodes[1], + /* modifiers = */ switchToNextLanguageShortcut.commands[0].keyCodes[0], + ) + + private val switchToNextLanguageShortcutAlternative = + shortcut("switch to next language") { + command(KeyEvent.META_CTRL_ON, KeyEvent.KEYCODE_SPACE) + } + + private val switchToNextLanguageKeyboardShortcutInfoAlternative = + KeyboardShortcutInfo( + /* label = */ switchToNextLanguageShortcutAlternative.label, + /* keycode = */ switchToNextLanguageShortcutAlternative.commands[0].keyCodes[1], + /* modifiers = */ switchToNextLanguageShortcutAlternative.commands[0].keyCodes[0], + ) + + private val switchToPreviousLanguageShortcut = + shortcut("switch to previous language") { + command( + KeyEvent.META_SHIFT_ON, + KeyEvent.KEYCODE_SPACE, + ) + } + + private val switchToPreviousLanguageKeyboardShortcutInfo = + KeyboardShortcutInfo( + /* label = */ switchToPreviousLanguageShortcut.label, + /* keycode = */ switchToPreviousLanguageShortcut.commands[0].keyCodes[1], + /* modifiers = */ switchToPreviousLanguageShortcut.commands[0].keyCodes[0], + ) + + private val switchToPreviousLanguageShortcutAlternative = + shortcut("switch to previous language") { + command( + KeyEvent.META_SHIFT_ON, + KeyEvent.KEYCODE_SPACE, + ) + } + + private val switchToPreviousLanguageKeyboardShortcutInfoAlternative = + KeyboardShortcutInfo( + /* label = */ switchToPreviousLanguageShortcutAlternative.label, + /* keycode = */ switchToPreviousLanguageShortcutAlternative.commands[0].keyCodes[1], + /* modifiers = */ switchToPreviousLanguageShortcutAlternative.commands[0].keyCodes[0], + ) + + private val showOnscreenKeyboardShortcut = + shortcut(label = "Show on-screen keyboard") { + command(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_K) + } + + private val showOnScreenKeyboardShortcutInfo = + KeyboardShortcutInfo( + /* label = */ showOnscreenKeyboardShortcut.label, + /* keycode = */ showOnscreenKeyboardShortcut.commands[0].keyCodes[1], + /* modifiers = */ showOnscreenKeyboardShortcut.commands[0].keyCodes[0], + ) + + private val accessClipboardShortcut = + shortcut(label = "Access clipboard") { command(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_V) } + + private val accessClipboardShortcutInfo = + KeyboardShortcutInfo( + /* label = */ accessClipboardShortcut.label, + /* keycode = */ accessClipboardShortcut.commands[0].keyCodes[1], + /* modifiers = */ accessClipboardShortcut.commands[0].keyCodes[0], + ) + + private val imeShortcutGroups = + listOf( + KeyboardShortcutGroup( + /* label = */ "input", + /* shortcutInfoList = */ listOf( + switchToNextLanguageKeyboardShortcutInfo, + switchToPreviousLanguageKeyboardShortcutInfo + ) + ) + ) + + private val imeShortcutCategory = + ShortcutCategory( + type = ShortcutCategoryType.IME, + subCategories = + listOf( + ShortcutSubCategory( + imeShortcutGroups[0].label.toString(), + listOf(switchToNextLanguageShortcut, switchToPreviousLanguageShortcut) + ) + ) + ) + + private val imeShortcutsGroupsWithDuplicateLabels = + listOf( + KeyboardShortcutGroup( + "input", + listOf( + switchToNextLanguageKeyboardShortcutInfo, + switchToNextLanguageKeyboardShortcutInfoAlternative, + switchToPreviousLanguageKeyboardShortcutInfo, + switchToPreviousLanguageKeyboardShortcutInfoAlternative + ) + ), + KeyboardShortcutGroup( + "Gboard", + listOf( + showOnScreenKeyboardShortcutInfo, + accessClipboardShortcutInfo, + ) + ) + ) + + private val expectedGroupedShortcutCategories = + ShortcutCategory( + type = ShortcutCategoryType.IME, + subCategories = + listOf( + ShortcutSubCategory( + imeShortcutsGroupsWithDuplicateLabels[0].label.toString(), + listOf( + switchToNextLanguageShortcut.copy( + commands = + switchToNextLanguageShortcut.commands + + switchToNextLanguageShortcutAlternative.commands + ), + switchToPreviousLanguageShortcut.copy( + commands = + switchToPreviousLanguageShortcut.commands + + switchToPreviousLanguageShortcutAlternative.commands + ) + ), + ), + ShortcutSubCategory( + imeShortcutsGroupsWithDuplicateLabels[1].label.toString(), + listOf(showOnscreenKeyboardShortcut, accessClipboardShortcut), + ) + ) + ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 90ac05fc1b2e..506c5aed203d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -135,7 +135,6 @@ class CustomizationProviderTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, - systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = @@ -481,8 +480,7 @@ class CustomizationProviderTest : SysuiTestCase() { ) } } - } - ?: emptyList() + } ?: emptyList() } private fun querySlots(): List<Slot> { @@ -517,8 +515,7 @@ class CustomizationProviderTest : SysuiTestCase() { ) } } - } - ?: emptyList() + } ?: emptyList() } private fun queryAffordances(): List<Affordance> { @@ -558,8 +555,7 @@ class CustomizationProviderTest : SysuiTestCase() { ) } } - } - ?: emptyList() + } ?: emptyList() } data class Slot( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index 27b9863e20fa..f726aae318df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -7,6 +7,7 @@ import android.graphics.Point import android.graphics.Rect import android.os.PowerManager import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper.RunWithLooper import android.view.RemoteAnimationTarget import android.view.SurfaceControl @@ -99,6 +100,13 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { mock(ActivityManager.RunningTaskInfo::class.java), false) private lateinit var wallpaperTargets: Array<RemoteAnimationTarget> + private var surfaceControlLockWp = mock(SurfaceControl::class.java) + private var lockWallpaperTarget = RemoteAnimationTarget( + 3 /* taskId */, 0, surfaceControlLockWp, false, Rect(), Rect(), 0, Point(), Rect(), + Rect(), mock(WindowConfiguration::class.java), false, surfaceControlLockWp, + Rect(), mock(ActivityManager.RunningTaskInfo::class.java), false) + private lateinit var lockWallpaperTargets: Array<RemoteAnimationTarget> + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -118,6 +126,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { // appear amount setter doesn't short circuit. remoteAnimationTargets = arrayOf(remoteTarget1) wallpaperTargets = arrayOf(wallpaperTarget) + lockWallpaperTargets = arrayOf(lockWallpaperTarget) // Set the surface applier to our mock so that we can verify the arguments passed to it. // This applier does not have any side effects within the unlock animation controller, so @@ -144,6 +153,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, arrayOf(), + arrayOf(), 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -177,6 +187,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, + arrayOf(), 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -199,6 +210,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, + arrayOf(), 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -219,6 +231,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, + arrayOf(), 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -242,6 +255,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, + arrayOf(), 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -265,6 +279,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, + arrayOf(), 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -286,6 +301,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, + arrayOf(), 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -301,6 +317,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, + arrayOf(), 0 /* startTime */, true /* requestedShowSurfaceBehindKeyguard */ ) @@ -317,6 +334,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, + arrayOf(), 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -325,6 +343,53 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { } /** + * The canned animation should launch a cross fade when there are different wallpapers on lock + * and home screen. + */ + @Test + @EnableFlags(Flags.FLAG_FASTER_UNLOCK_TRANSITION) + fun manualUnlock_multipleWallpapers() { + var lastFadeInAlpha = -1f + var lastFadeOutAlpha = -1f + + keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( + arrayOf(remoteTarget1, remoteTarget2), + wallpaperTargets, + lockWallpaperTargets, + 0 /* startTime */, + false /* requestedShowSurfaceBehindKeyguard */ + ) + + for (i in 0..10) { + clearInvocations(surfaceTransactionApplier) + val amount = i / 10f + + keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(amount) + + val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>() + verify(surfaceTransactionApplier, times(2)).scheduleApply( + captorSb.capture { sp -> + sp.surface == surfaceControlWp || sp.surface == surfaceControlLockWp }) + + val fadeInAlpha = captorSb.getLastValue { it.surface == surfaceControlWp }.alpha + val fadeOutAlpha = captorSb.getLastValue { it.surface == surfaceControlLockWp }.alpha + + if (amount == 0f) { + assertTrue (fadeInAlpha == 0f) + assertTrue (fadeOutAlpha == 1f) + } else if (amount == 1f) { + assertTrue (fadeInAlpha == 1f) + assertTrue (fadeOutAlpha == 0f) + } else { + assertTrue(fadeInAlpha >= lastFadeInAlpha) + assertTrue(fadeOutAlpha <= lastFadeOutAlpha) + } + lastFadeInAlpha = fadeInAlpha + lastFadeOutAlpha = fadeOutAlpha + } + } + + /** * If we are not wake and unlocking, we expect the unlock animation to play normally. */ @Test @@ -333,6 +398,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( arrayOf(remoteTarget1, remoteTarget2), wallpaperTargets, + arrayOf(), 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -378,6 +444,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, + arrayOf(), 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -387,7 +454,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { clearInvocations(surfaceTransactionApplier) keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f) - keyguardUnlockAnimationController.setWallpaperAppearAmount(1f) + keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets) val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>() verify(surfaceTransactionApplier, times(1)).scheduleApply( @@ -414,6 +481,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( remoteAnimationTargets, wallpaperTargets, + arrayOf(), 0 /* startTime */, false /* requestedShowSurfaceBehindKeyguard */ ) @@ -423,7 +491,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { clearInvocations(surfaceTransactionApplier) keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f) - keyguardUnlockAnimationController.setWallpaperAppearAmount(1f) + keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets) val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>() verify(surfaceTransactionApplier, times(1)).scheduleApply( @@ -532,8 +600,8 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { } } - fun getLastValue(): T { - return allArgs.last() + fun getLastValue(predicate: Predicate<T>? = null): T { + return if (predicate != null) allArgs.last(predicate::test) else allArgs.last() } fun getAllValues(): List<T> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index d2a9c582d904..7560a970851e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -63,9 +63,6 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before -import platform.test.runner.parameterized.ParameterizedAndroidJunit4 -import platform.test.runner.parameterized.Parameters -import platform.test.runner.parameterized.Parameter import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt @@ -76,6 +73,9 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.Parameter +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @FlakyTest( @@ -281,7 +281,6 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, - systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt index 9d06031a3ed5..fd1bf5401784 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt @@ -63,9 +63,6 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before -import platform.test.runner.parameterized.ParameterizedAndroidJunit4 -import platform.test.runner.parameterized.Parameters -import platform.test.runner.parameterized.Parameter import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt @@ -76,6 +73,9 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.Parameter +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @FlakyTest( @@ -281,7 +281,6 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, - systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index bdc5fc34158f..4f4aac454912 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -46,8 +46,8 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTouchHandlingInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition @@ -58,6 +58,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.pulsingGestureListener import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.statusbar.policy.KeyguardStateController @@ -178,7 +179,6 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, - systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = @@ -211,8 +211,8 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC dumpManager = mock(), userHandle = UserHandle.SYSTEM, ) - val keyguardLongPressInteractor = - KeyguardLongPressInteractor( + val keyguardTouchHandlingInteractor = + KeyguardTouchHandlingInteractor( appContext = mContext, scope = testScope.backgroundScope, transitionInteractor = kosmos.keyguardTransitionInteractor, @@ -221,6 +221,7 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC featureFlags = featureFlags, broadcastDispatcher = broadcastDispatcher, accessibilityManager = accessibilityManager, + pulsingGestureListener = kosmos.pulsingGestureListener, ) underTest = KeyguardBottomAreaViewModel( @@ -246,13 +247,13 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC ), bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository), burnInHelperWrapper = burnInHelperWrapper, - longPressViewModel = - KeyguardLongPressViewModel( - interactor = keyguardLongPressInteractor, + keyguardTouchHandlingViewModel = + KeyguardTouchHandlingViewModel( + interactor = keyguardTouchHandlingInteractor, ), settingsMenuViewModel = KeyguardSettingsMenuViewModel( - interactor = keyguardLongPressInteractor, + interactor = keyguardTouchHandlingInteractor, ), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index e33d75c02052..9fb1aa795c3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -221,7 +221,6 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, - systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt index a77072217873..fbfe41f57927 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt @@ -1799,6 +1799,7 @@ public class MediaControlPanelTest : SysuiTestCase() { any(), eq(null), eq(null), + eq(null), ) verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java index fbfd35fd5b9c..c7a92d2a390d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java @@ -36,7 +36,7 @@ import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.settings.FakeDisplayTracker; -import com.android.systemui.statusbar.phone.BarTransitions; +import com.android.systemui.shared.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.LightBarTransitionsController; import com.android.systemui.statusbar.policy.KeyguardStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt index 5ed8a11e862f..eae6cdbe4d2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt @@ -7,6 +7,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.model.SysUiState import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.recents.OverviewProxyService import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.shared.system.TaskStackChangeListeners @@ -70,6 +71,8 @@ class TaskbarDelegateTest : SysuiTestCase() { lateinit var mCurrentSysUiState: NavBarHelper.CurrentSysuiState @Mock lateinit var mStatusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock + lateinit var mStatusBarStateController: StatusBarStateController @Before fun setup() { @@ -80,7 +83,7 @@ class TaskbarDelegateTest : SysuiTestCase() { `when`(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState) mTaskStackChangeListeners = TaskStackChangeListeners.getTestInstance() mTaskbarDelegate = TaskbarDelegate(context, mLightBarControllerFactory, - mStatusBarKeyguardViewManager) + mStatusBarKeyguardViewManager, mStatusBarStateController) mTaskbarDelegate.setDependencies(mCommandQueue, mOverviewProxyService, mNavBarHelper, mNavigationModeController, mSysUiState, mDumpManager, mAutoHideController, mLightBarController, mOptionalPip, mBackAnimation, mTaskStackChangeListeners) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt index 42b81de92af7..c918ed82604c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt @@ -97,6 +97,7 @@ class QSTileViewModelImplTest : SysuiTestCase() { qsTileLogger, FakeSystemClock(), testCoroutineDispatcher, + testCoroutineDispatcher, testScope.backgroundScope, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index 6e6e31177564..e1c39117f6c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -23,6 +23,7 @@ import android.content.pm.ResolveInfo import android.os.PowerManager import android.os.Process import android.os.UserHandle +import android.os.UserManager import android.testing.TestableContext import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -108,6 +109,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { @Mock private lateinit var navModeController: NavigationModeController @Mock private lateinit var statusBarWinController: NotificationShadeWindowController @Mock private lateinit var userTracker: UserTracker + @Mock private lateinit var userManager: UserManager @Mock private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var sysuiUnlockAnimationController: KeyguardUnlockAnimationController @Mock @@ -199,11 +201,12 @@ class OverviewProxyServiceTest : SysuiTestCase() { } @Test - fun connectToOverviewService_primaryUser_expectBindService() { + fun connectToOverviewService_primaryUserNoVisibleBgUsersSupported_expectBindService() { val mockitoSession = ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking() try { `when`(Process.myUserHandle()).thenReturn(UserHandle.SYSTEM) + `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false) val spyContext = spy(context) val ops = createOverviewProxyService(spyContext) ops.startConnectionToCurrentUser() @@ -214,11 +217,46 @@ class OverviewProxyServiceTest : SysuiTestCase() { } @Test - fun connectToOverviewService_nonPrimaryUser_expectNoBindService() { + fun connectToOverviewService_nonPrimaryUserNoVisibleBgUsersSupported_expectNoBindService() { val mockitoSession = ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking() try { `when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345)) + `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false) + val spyContext = spy(context) + val ops = createOverviewProxyService(spyContext) + ops.startConnectionToCurrentUser() + verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any()) + } finally { + mockitoSession.finishMocking() + } + } + + @Test + fun connectToOverviewService_nonPrimaryBgUserVisibleBgUsersSupported_expectBindService() { + val mockitoSession = + ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking() + try { + `when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345)) + `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true) + `when`(userManager.isUserForeground()).thenReturn(false) + val spyContext = spy(context) + val ops = createOverviewProxyService(spyContext) + ops.startConnectionToCurrentUser() + verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(), anyInt(), any()) + } finally { + mockitoSession.finishMocking() + } + } + + @Test + fun connectToOverviewService_nonPrimaryFgUserVisibleBgUsersSupported_expectNoBindService() { + val mockitoSession = + ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking() + try { + `when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345)) + `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true) + `when`(userManager.isUserForeground()).thenReturn(true) val spyContext = spy(context) val ops = createOverviewProxyService(spyContext) ops.startConnectionToCurrentUser() @@ -242,6 +280,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { sysUiState, mock(), userTracker, + userManager, wakefulnessLifecycle, uiEventLogger, displayTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java index 6733ead1f1ca..809fb3f43fd5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java @@ -26,6 +26,7 @@ import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE; import static com.android.internal.infra.AndroidFuture.completedFuture; import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED; +import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CLIP_DATA; import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI; import static com.google.common.truth.Truth.assertThat; @@ -37,6 +38,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Activity; +import android.content.ClipData; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -81,6 +83,7 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { private static final String TEST_URI_STRING = "www.test-uri.com"; private static final Uri TEST_URI = Uri.parse(TEST_URI_STRING); + private static final ClipData TEST_CLIP_DATA = ClipData.newRawUri("Test backlinks", TEST_URI); private static final int TEST_UID = 42; private static final String TEST_CALLING_PACKAGE = "test-calling-package"; @@ -238,6 +241,7 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { Bundle bundle = new Bundle(); bundle.putParcelable(EXTRA_SCREENSHOT_URI, TEST_URI); bundle.putInt(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, CAPTURE_CONTENT_FOR_NOTE_SUCCESS); + bundle.putParcelable(EXTRA_CLIP_DATA, TEST_CLIP_DATA); activity.getResultReceiverForTest().send(Activity.RESULT_OK, bundle); waitForIdleSync(); @@ -245,7 +249,10 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK); assertThat(getStatusCodeExtra(actualResult.getResultData())) .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_SUCCESS); - assertThat(actualResult.getResultData().getData()).isEqualTo(TEST_URI); + + Intent resultData = actualResult.getResultData(); + assertThat(resultData.getData()).isEqualTo(TEST_URI); + assertThat(resultData.getClipData()).isEqualTo(TEST_CLIP_DATA); assertThat(mActivityRule.getActivity().isFinishing()).isTrue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 15c4bfce0a04..e7ca091aaf4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -111,7 +111,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransition import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; -import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel; +import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel; @@ -334,7 +334,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor; - @Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel; + @Mock protected KeyguardTouchHandlingViewModel mKeyuardTouchHandlingViewModel; @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock protected MotionEvent mDownMotionEvent; @Mock protected CoroutineDispatcher mMainDispatcher; @@ -755,7 +755,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mMainDispatcher, mKeyguardTransitionInteractor, mDumpManager, - mKeyuardLongPressViewModel, + mKeyuardTouchHandlingViewModel, mKeyguardInteractor, mActivityStarter, mSharedNotificationContainerInteractor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index e984200c305e..a7f36c317f3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt @@ -20,6 +20,7 @@ import android.Manifest.permission import android.app.Notification.CATEGORY_EVENT import android.app.Notification.CATEGORY_REMINDER import android.app.NotificationManager +import android.content.pm.PackageManager.PERMISSION_DENIED import android.content.pm.PackageManager.PERMISSION_GRANTED import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -28,11 +29,16 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE -import java.util.Optional import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mockito.anyString +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.Mockito.`when` +import org.mockito.kotlin.whenever +import java.util.Optional @SmallTest @RunWith(AndroidJUnit4::class) @@ -58,7 +64,9 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider, systemSettings, packageManager, - Optional.of(bubbles) + Optional.of(bubbles), + context, + notificationManager ) } @@ -87,12 +95,60 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro // because avalanche code is based on the suppression refactor. @Test + fun testAvalancheFilter_suppress_hasNotSeenEdu_showEduHun() { + setAllowedEmergencyPkg(false) + whenever(avalancheProvider.timeoutMs).thenReturn(20) + whenever(avalancheProvider.startTime).thenReturn(whenAgo(10)) + + val avalancheSuppressor = AvalancheSuppressor( + avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger, context, notificationManager + ) + avalancheSuppressor.hasSeenEdu = false + + withFilter(avalancheSuppressor) { + ensurePeekState() + assertShouldNotHeadsUp( + buildEntry { + importance = NotificationManager.IMPORTANCE_HIGH + whenMs = whenAgo(5) + } + ) + } + verify(notificationManager, times(1)).notify(anyInt(), any()) + } + + @Test + fun testAvalancheFilter_suppress_hasSeenEduHun_doNotShowEduHun() { + setAllowedEmergencyPkg(false) + whenever(avalancheProvider.timeoutMs).thenReturn(20) + whenever(avalancheProvider.startTime).thenReturn(whenAgo(10)) + + val avalancheSuppressor = AvalancheSuppressor( + avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger, context, notificationManager + ) + avalancheSuppressor.hasSeenEdu = true + + withFilter(avalancheSuppressor) { + ensurePeekState() + assertShouldNotHeadsUp( + buildEntry { + importance = NotificationManager.IMPORTANCE_HIGH + whenMs = whenAgo(5) + } + ) + } + verify(notificationManager, times(0)).notify(anyInt(), any()) + } + + @Test fun testAvalancheFilter_duringAvalanche_allowConversationFromAfterEvent() { avalancheProvider.startTime = whenAgo(10) withFilter( AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, - uiEventLogger) + uiEventLogger, context, notificationManager) ) { ensurePeekState() assertShouldHeadsUp( @@ -112,7 +168,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro withFilter( AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, - uiEventLogger) + uiEventLogger, context, notificationManager) ) { ensurePeekState() assertShouldNotHeadsUp( @@ -132,7 +188,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro withFilter( AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, - uiEventLogger) + uiEventLogger, context, notificationManager) ) { ensurePeekState() assertShouldHeadsUp( @@ -150,7 +206,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro withFilter( AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, - uiEventLogger) + uiEventLogger, context, notificationManager) ) { ensurePeekState() assertShouldHeadsUp( @@ -168,7 +224,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro withFilter( AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, - uiEventLogger) + uiEventLogger, context, notificationManager) ) { ensurePeekState() assertShouldHeadsUp( @@ -186,7 +242,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro withFilter( AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, - uiEventLogger) + uiEventLogger, context, notificationManager) ) { ensurePeekState() assertShouldHeadsUp( @@ -204,7 +260,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro withFilter( AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, - uiEventLogger) + uiEventLogger, context, notificationManager) ) { assertFsiNotSuppressed() } @@ -216,7 +272,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro withFilter( AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, - uiEventLogger) + uiEventLogger, context, notificationManager) ) { ensurePeekState() assertShouldHeadsUp( @@ -228,20 +284,24 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro } } - @Test - fun testAvalancheFilter_duringAvalanche_allowEmergency() { - avalancheProvider.startTime = whenAgo(10) - + private fun setAllowedEmergencyPkg(allow: Boolean) { `when`( packageManager.checkPermission( org.mockito.Mockito.eq(permission.RECEIVE_EMERGENCY_BROADCAST), anyString() ) - ).thenReturn(PERMISSION_GRANTED) + ).thenReturn(if (allow) PERMISSION_GRANTED else PERMISSION_DENIED) + } + + @Test + fun testAvalancheFilter_duringAvalanche_allowEmergency() { + avalancheProvider.startTime = whenAgo(10) + + setAllowedEmergencyPkg(true) withFilter( AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, - uiEventLogger) + uiEventLogger, context, notificationManager) ) { ensurePeekState() assertShouldHeadsUp( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt index a45740502012..378705a3c1a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt @@ -31,6 +31,7 @@ import android.app.Notification.GROUP_ALERT_CHILDREN import android.app.Notification.GROUP_ALERT_SUMMARY import android.app.Notification.VISIBILITY_PRIVATE import android.app.NotificationChannel +import android.app.NotificationManager import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.IMPORTANCE_LOW @@ -133,7 +134,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { protected val bubbles: Bubbles = mock() lateinit var systemSettings: SystemSettings protected val packageManager: PackageManager = mock() - + protected val notificationManager: NotificationManager = mock() protected abstract val provider: VisualInterruptionDecisionProvider private val neverSuppresses = object : NotificationInterruptSuppressor {} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt index 01e638b0ab17..f4cebd7d5d3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt @@ -15,6 +15,8 @@ */ package com.android.systemui.statusbar.notification.interruption +import android.app.NotificationManager +import android.content.Context import android.content.pm.PackageManager import android.hardware.display.AmbientDisplayConfiguration import android.os.Handler @@ -58,6 +60,8 @@ object VisualInterruptionDecisionProviderTestUtil { systemSettings: SystemSettings, packageManager: PackageManager, bubbles: Optional<Bubbles>, + context: Context, + notificationManager: NotificationManager ): VisualInterruptionDecisionProvider { return if (VisualInterruptionRefactor.isEnabled) { VisualInterruptionDecisionProviderImpl( @@ -79,7 +83,9 @@ object VisualInterruptionDecisionProviderTestUtil { avalancheProvider, systemSettings, packageManager, - bubbles + bubbles, + context, + notificationManager ) } else { NotificationInterruptStateProviderWrapper( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 164a06e68dc3..e8349b0978e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -862,11 +862,12 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME) - public void isExpanded_systemExpandedTrueForHeadsUp_notExpanded() throws Exception { + public void isExpanded_HUNsystemExpandedTrueForPinned_notExpanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); row.setOnKeyguard(false); row.setSystemExpanded(true); + row.setPinned(true); row.setHeadsUp(true); // THEN @@ -875,12 +876,27 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME) - public void isExpanded_systemExpandedTrueForHeadsUpDisappearRunning_notExpanded() + public void isExpanded_HUNsystemExpandedTrueForNotPinned_expanded() throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setOnKeyguard(false); + row.setSystemExpanded(true); + row.setPinned(false); + row.setHeadsUp(true); + + // THEN + assertThat(row.isExpanded()).isTrue(); + } + + @Test + @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME) + public void isExpanded_HUNDisappearingsystemExpandedTrueForPinned_notExpanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); row.setOnKeyguard(false); row.setSystemExpanded(true); + row.setPinned(true); row.setHeadsUpAnimatingAway(true); // THEN @@ -889,6 +905,21 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @Test @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME) + public void isExpanded_HUNDisappearingsystemExpandedTrueForNotPinned_expanded() + throws Exception { + // GIVEN + final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); + row.setOnKeyguard(false); + row.setSystemExpanded(true); + row.setPinned(false); + row.setHeadsUpAnimatingAway(true); + + // THEN + assertThat(row.isExpanded()).isTrue(); + } + + @Test + @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME) public void isExpanded_userExpandedTrueForHeadsUp_expanded() throws Exception { // GIVEN final ExpandableNotificationRow row = mNotificationTestHelper.createRow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 1eb33ce8d479..d2540a64bd79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -53,6 +53,7 @@ import static java.util.Collections.emptySet; import android.app.ActivityManager; import android.app.IWallpaperManager; +import android.app.NotificationManager; import android.app.WallpaperManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; @@ -339,6 +340,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private KeyboardShortcuts mKeyboardShortcuts; @Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch; @Mock private PackageManager mPackageManager; + @Mock private NotificationManager mNotificationManager; @Mock private GlanceableHubContainerController mGlanceableHubContainerController; @Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory; @@ -399,7 +401,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mAvalancheProvider, mSystemSettings, mPackageManager, - Optional.of(mBubbles)); + Optional.of(mBubbles), + mContext, + mNotificationManager); mVisualInterruptionDecisionProvider.start(); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index a27073c77eb4..88ec18dd65f3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.phone; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; -import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT; +import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT; import static junit.framework.Assert.assertTrue; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt index c4568a9ecfe6..318656b1a94d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt @@ -21,12 +21,12 @@ import android.view.View import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase -import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT -import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT -import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE -import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT -import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT -import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT +import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT +import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT +import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE +import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT +import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSLUCENT +import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt new file mode 100644 index 000000000000..230ddf9d25db --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.testKosmos +import com.android.systemui.util.kotlin.getValue +import com.google.common.truth.Truth.assertThat +import dagger.Lazy +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +class StatusBarTouchableRegionManagerTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneRepository = kosmos.sceneContainerRepository + + private val underTest by Lazy { kosmos.statusBarTouchableRegionManager } + + @Test + @EnableSceneContainer + fun entireScreenTouchable_sceneContainerEnabled_isRemoteUserInteractionOngoing() = + testScope.runTest { + sceneRepository.setTransitionState( + flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone)) + ) + runCurrent() + assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() + + sceneRepository.isRemoteUserInteractionOngoing.value = true + runCurrent() + assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue() + + sceneRepository.isRemoteUserInteractionOngoing.value = false + runCurrent() + assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() + } + + @Test + @DisableSceneContainer + fun entireScreenTouchable_sceneContainerDisabled_isRemoteUserInteractionOngoing() = + testScope.runTest { + assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() + + sceneRepository.isRemoteUserInteractionOngoing.value = true + runCurrent() + + assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() + } + + @Test + @EnableSceneContainer + fun entireScreenTouchable_sceneContainerEnabled_isIdleOnGone() = + testScope.runTest { + sceneRepository.setTransitionState( + flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone)) + ) + runCurrent() + assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() + + sceneRepository.setTransitionState( + flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade)) + ) + runCurrent() + assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue() + + sceneRepository.setTransitionState( + flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Gone)) + ) + runCurrent() + assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() + } + + @Test + @DisableSceneContainer + fun entireScreenTouchable_sceneContainerDisabled_isIdleOnGone() = + testScope.runTest { + assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() + + sceneRepository.setTransitionState( + flowOf(ObservableTransitionState.Idle(currentScene = Scenes.Shade)) + ) + runCurrent() + + assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index ee27cea48565..01540e7584a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -49,6 +49,8 @@ import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.animation.AnimatorTestRule; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.DisableSceneContainer; +import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.LogcatEchoTracker; import com.android.systemui.plugins.DarkIconDispatcher; @@ -302,6 +304,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableSceneContainer public void disable_shadeOpenAndShouldHide_everythingHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -318,6 +321,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableSceneContainer public void disable_shadeOpenButNotShouldHide_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -335,6 +339,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { /** Regression test for b/279790651. */ @Test + @DisableSceneContainer public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -376,6 +381,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableSceneContainer public void disable_isTransitioningToOccluded_everythingHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -390,6 +396,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableSceneContainer public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -674,6 +681,80 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @EnableSceneContainer + public void isHomeStatusBarAllowedByScene_false_everythingHidden() { + resumeAndGetFragment(); + + mCollapsedStatusBarViewBinder.getListener().onIsHomeStatusBarAllowedBySceneChanged(false); + + // THEN all views are hidden + assertEquals(View.GONE, getClockView().getVisibility()); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); + assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); + } + + @Test + @EnableSceneContainer + public void isHomeStatusBarAllowedByScene_true_everythingShown() { + resumeAndGetFragment(); + + mCollapsedStatusBarViewBinder.getListener().onIsHomeStatusBarAllowedBySceneChanged(true); + + // THEN all views are shown + assertEquals(View.VISIBLE, getClockView().getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + } + + @Test + @EnableSceneContainer + public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + // WHEN the scene doesn't allow the status bar + mCollapsedStatusBarViewBinder.getListener().onIsHomeStatusBarAllowedBySceneChanged(false); + + // BUT the disable flags want to show the status bar + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // THEN all views are hidden (the disable flags aren't respected) + assertEquals(View.GONE, getClockView().getVisibility()); + assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); + assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); + } + + @Test + @EnableSceneContainer + public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + // WHEN the scene does allow the status bar + mCollapsedStatusBarViewBinder.getListener().onIsHomeStatusBarAllowedBySceneChanged(true); + + // AND the disable flags want to hide the clock + fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false); + + // THEN all views are shown except the clock (the disable flags are used) + assertEquals(View.GONE, getClockView().getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + } + + @Test + @DisableSceneContainer + public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() { + resumeAndGetFragment(); + + // Even if the scene says to hide the home status bar + mCollapsedStatusBarViewBinder.getListener().onIsHomeStatusBarAllowedBySceneChanged(false); + + // The value isn't used because the scene container flag is disabled, so all views are shown + assertEquals(View.VISIBLE, getClockView().getVisibility()); + assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility()); + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + } + + @Test public void disable_isDozing_clockAndSystemInfoVisible() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); @@ -758,6 +839,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableSceneContainer public void testStatusBarIcons_hiddenThroughoutCameraLaunch() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -779,6 +861,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + @DisableSceneContainer public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() { final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index b4c6106150b2..94159bcebf47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -24,6 +24,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState @@ -36,6 +37,10 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.log.assertLogsWtf import com.android.systemui.mediaprojection.data.model.MediaProjectionState import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE @@ -84,6 +89,8 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { kosmos.lightsOutInteractor, kosmos.activeNotificationsInteractor, kosmos.keyguardTransitionInteractor, + kosmos.sceneInteractor, + kosmos.sceneContainerOcclusionInteractor, kosmos.ongoingActivityChipsViewModel, kosmos.applicationCoroutineScope, ) @@ -426,6 +433,68 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { assertIsShareToAppChip(latest) } + @Test + fun isHomeStatusBarAllowedByScene_sceneLockscreen_notOccluded_false() = + testScope.runTest { + val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen) + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, taskInfo = null) + + assertThat(latest).isFalse() + } + + @Test + fun isHomeStatusBarAllowedByScene_sceneLockscreen_occluded_true() = + testScope.runTest { + val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen) + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, taskInfo = null) + + assertThat(latest).isTrue() + } + + @Test + fun isHomeStatusBarAllowedByScene_sceneBouncer_false() = + testScope.runTest { + val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Bouncer) + + assertThat(latest).isFalse() + } + + @Test + fun isHomeStatusBarAllowedByScene_sceneCommunal_false() = + testScope.runTest { + val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Communal) + + assertThat(latest).isFalse() + } + + @Test + fun isHomeStatusBarAllowedByScene_sceneShade_false() = + testScope.runTest { + val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Shade) + + assertThat(latest).isFalse() + } + + @Test + fun isHomeStatusBarAllowedByScene_sceneGone_true() = + testScope.runTest { + val latest by collectLastValue(underTest.isHomeStatusBarAllowedByScene) + + kosmos.sceneContainerRepository.snapToScene(Scenes.Gone) + + assertThat(latest).isTrue() + } + private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) = ActiveNotificationsStore.Builder() .apply { notifications.forEach(::addIndividualNotif) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt index b66eed05fa0d..d3f11253fc09 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt @@ -31,6 +31,8 @@ class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel { override val ongoingActivityChip: MutableStateFlow<OngoingActivityChipModel> = MutableStateFlow(OngoingActivityChipModel.Hidden) + override val isHomeStatusBarAllowedByScene = MutableStateFlow(false) + override fun areNotificationsLightsOut(displayId: Int): Flow<Boolean> = areNotificationLightsOut fun setNotificationLightsOut(lightsOut: Boolean) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 70afbd82df11..ffe7750dadfa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -247,7 +247,7 @@ public class RemoteInputViewTest extends SysuiTestCase { ExpandableNotificationRow row = helper.createRow(); RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); - view.addOnVisibilityChangedListener(null); + view.setOnVisibilityChangedListener(null); view.setVisibility(View.INVISIBLE); view.setVisibility(View.VISIBLE); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt index 97688d55b778..ef2d4ce2becc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt @@ -29,10 +29,16 @@ import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING import com.android.systemui.unfold.util.TestFoldStateProvider import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +/** + * This test class tests [PhysicsBasedUnfoldTransitionProgressProvider] in a more E2E + * fashion, it uses real handler thread and timings, so it might be perceptible to more flakiness + * compared to the other unit tests that do not perform real multithreaded interactions. + */ @RunWith(AndroidJUnit4::class) @SmallTest class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @@ -44,8 +50,8 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { mock<UnfoldFrameCallbackScheduler.Factory>().apply { whenever(create()).then { UnfoldFrameCallbackScheduler() } } - private val mockBgHandler = mock<Handler>() - private val fakeHandler = Handler(HandlerThread("UnfoldBg").apply { start() }.looper) + private val handlerThread = HandlerThread("UnfoldBg").apply { start() } + private val bgHandler = Handler(handlerThread.looper) @Before fun setUp() { @@ -54,20 +60,26 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { context, schedulerFactory, foldStateProvider = foldStateProvider, - progressHandler = fakeHandler + progressHandler = bgHandler ) progressProvider.addCallback(listener) } + @After + fun after() { + handlerThread.quit() + } + @Test fun testUnfold_emitsIncreasingTransitionEvents() { runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendHingeAngleUpdate(10f) }, - { foldStateProvider.sendUnfoldedScreenAvailable() }, - { foldStateProvider.sendHingeAngleUpdate(90f) }, - { foldStateProvider.sendHingeAngleUpdate(180f) }, - { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }, + { foldStateProvider.sendUnfoldedScreenAvailable() } + ) + sendHingeAngleAndEnsureAnimationUpdate(90f, 120f, 180f) + runOnProgressThreadWithInterval( + { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) } ) with(listener.ensureTransitionFinished()) { @@ -91,7 +103,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { } @Test - fun testUnfold_screenAvailableOnlyAfterFullUnfold_emitsIncreasingTransitionEvents() { + fun testUnfold_screenAvailableOnlyAfterFullUnfold_finishesWithUnfoldEvent() { runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, { foldStateProvider.sendHingeAngleUpdate(10f) }, @@ -102,7 +114,6 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { ) with(listener.ensureTransitionFinished()) { - assertIncreasingProgress() assertFinishedWithUnfold() } } @@ -111,9 +122,9 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { fun testFold_emitsDecreasingTransitionEvents() { runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING) }, - { foldStateProvider.sendHingeAngleUpdate(170f) }, - { foldStateProvider.sendHingeAngleUpdate(90f) }, - { foldStateProvider.sendHingeAngleUpdate(10f) }, + ) + sendHingeAngleAndEnsureAnimationUpdate(170f, 90f, 10f) + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) }, ) @@ -127,9 +138,9 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() { runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) }, - { foldStateProvider.sendUnfoldedScreenAvailable() }, - { foldStateProvider.sendHingeAngleUpdate(10f) }, - { foldStateProvider.sendHingeAngleUpdate(90f) }, + { foldStateProvider.sendUnfoldedScreenAvailable() }) + sendHingeAngleAndEnsureAnimationUpdate(10f, 50f, 90f) + runOnProgressThreadWithInterval( { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) }, ) @@ -159,12 +170,22 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { with(listener.ensureTransitionFinished()) { assertHasFoldAnimationAtTheEnd() } } + private fun sendHingeAngleAndEnsureAnimationUpdate(vararg angles: Float) { + angles.forEach { angle -> + listener.waitForProgressChangeAfter { + bgHandler.post { + foldStateProvider.sendHingeAngleUpdate(angle) + } + } + } + } + private fun runOnProgressThreadWithInterval( vararg blocks: () -> Unit, intervalMillis: Long = 60, ) { blocks.forEach { - fakeHandler.post(it) + bgHandler.post(it) Thread.sleep(intervalMillis) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt index bbc96f703738..6e8bf85c9bcc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt @@ -68,6 +68,24 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr return recordings.first() } + /** + * Number of progress event for the currently running transition + * Returns null if there is no currently running transition + */ + val currentTransitionProgressEventCount: Int? + get() = currentRecording?.progressHistory?.size + + /** + * Runs [block] and ensures that there was at least once onTransitionProgress event after that + */ + fun waitForProgressChangeAfter(block: () -> Unit) { + val eventCount = currentTransitionProgressEventCount + block() + waitForCondition { + currentTransitionProgressEventCount != eventCount + } + } + fun assertStarted() { assertWithMessage("Transition didn't start").that(currentRecording).isNotNull() } @@ -86,7 +104,7 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr } class UnfoldTransitionRecording { - private val progressHistory: MutableList<Float> = arrayListOf() + val progressHistory: MutableList<Float> = arrayListOf() private var finishingInvocations: Int = 0 fun addProgress(progress: Float) { @@ -142,6 +160,6 @@ class TestUnfoldProgressListener : UnfoldTransitionProgressProvider.TransitionPr } private companion object { - private const val MIN_ANIMATION_EVENTS = 5 + private const val MIN_ANIMATION_EVENTS = 3 } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index c5fbc39e79c3..dc7a2c320855 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -59,6 +59,7 @@ import android.app.ActivityManager; import android.app.IActivityManager; import android.app.INotificationManager; import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; @@ -473,7 +474,9 @@ public class BubblesTest extends SysuiTestCase { mock(AvalancheProvider.class), mock(SystemSettings.class), mock(PackageManager.class), - Optional.of(mock(Bubbles.class)) + Optional.of(mock(Bubbles.class)), + mContext, + mock(NotificationManager.class) ); interruptionDecisionProvider.start(); diff --git a/packages/SystemUI/tests/utils/src/android/hardware/display/AmbientDisplayConfigurationKosmos.kt b/packages/SystemUI/tests/utils/src/android/hardware/display/AmbientDisplayConfigurationKosmos.kt new file mode 100644 index 000000000000..3f3c30f0faec --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/hardware/display/AmbientDisplayConfigurationKosmos.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display + +import android.content.applicationContext +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.ambientDisplayConfiguration by Fixture { + FakeAmbientDisplayConfiguration(applicationContext) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 78d8abeb477a..a124b34cde85 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -43,7 +43,7 @@ import androidx.core.animation.AndroidXAnimatorIsolationRule; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.systemui.broadcast.FakeBroadcastDispatcher; import com.android.systemui.flags.SceneContainerRule; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt index f51036f3b54b..e00f9806f6a2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyboard.shortcut import android.content.applicationContext import android.content.res.mainResources import android.hardware.input.fakeInputManager +import android.view.windowManager import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository @@ -59,6 +60,8 @@ val Kosmos.shortcutHelperCategoriesRepository by ShortcutHelperCategoriesRepository( shortcutHelperSystemShortcutsSource, shortcutHelperMultiTaskingShortcutsSource, + windowManager, + shortcutHelperStateRepository ) } @@ -68,7 +71,8 @@ val Kosmos.shortcutHelperTestHelper by shortcutHelperStateRepository, applicationContext, broadcastDispatcher, - fakeCommandQueue + fakeCommandQueue, + windowManager ) } @@ -83,12 +87,7 @@ val Kosmos.shortcutHelperStateInteractor by } val Kosmos.shortcutHelperCategoriesInteractor by - Kosmos.Fixture { - ShortcutHelperCategoriesInteractor( - shortcutHelperStateRepository, - shortcutHelperCategoriesRepository - ) - } + Kosmos.Fixture { ShortcutHelperCategoriesInteractor(shortcutHelperCategoriesRepository) } val Kosmos.shortcutHelperViewModel by Kosmos.Fixture { ShortcutHelperViewModel(testDispatcher, shortcutHelperStateInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt index 36608ff57c93..40510db24f47 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt @@ -18,20 +18,45 @@ package com.android.systemui.keyboard.shortcut.data.repository import android.content.Context import android.content.Intent +import android.view.KeyboardShortcutGroup +import android.view.WindowManager +import android.view.WindowManager.KeyboardShortcutsReceiver import com.android.systemui.broadcast.FakeBroadcastDispatcher import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever class ShortcutHelperTestHelper( repo: ShortcutHelperStateRepository, private val context: Context, private val fakeBroadcastDispatcher: FakeBroadcastDispatcher, private val fakeCommandQueue: FakeCommandQueue, + windowManager: WindowManager ) { + companion object { + const val DEFAULT_DEVICE_ID = 123 + } + + private var imeShortcuts: List<KeyboardShortcutGroup> = emptyList() + init { + whenever(windowManager.requestImeKeyboardShortcuts(any(), any())).thenAnswer { + val keyboardShortcutReceiver = it.getArgument<KeyboardShortcutsReceiver>(0) + keyboardShortcutReceiver.onKeyboardShortcutsReceived(imeShortcuts) + return@thenAnswer Unit + } repo.start() } + /** + * Use this method to set what ime shortcuts should be returned from windowManager in tests. By + * default windowManager.requestImeKeyboardShortcuts will return emptyList. See init block. + */ + fun setImeShortcuts(imeShortcuts: List<KeyboardShortcutGroup>) { + this.imeShortcuts = imeShortcuts + } + fun hideThroughCloseSystemDialogs() { fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( context, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt index c06f833c9e96..73799b63a6fc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt @@ -24,10 +24,11 @@ import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.shade.pulsingGestureListener -val Kosmos.keyguardLongPressInteractor by +val Kosmos.keyguardTouchHandlingInteractor by Kosmos.Fixture { - KeyguardLongPressInteractor( + KeyguardTouchHandlingInteractor( appContext = applicationContext, scope = applicationCoroutineScope, transitionInteractor = keyguardTransitionInteractor, @@ -36,5 +37,6 @@ val Kosmos.keyguardLongPressInteractor by featureFlags = featureFlagsClassic, broadcastDispatcher = broadcastDispatcher, accessibilityManager = accessibilityManagerWrapper, + pulsingGestureListener = pulsingGestureListener, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt index 3c9846acf28c..281d7b051d50 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt @@ -16,12 +16,12 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.keyguard.domain.interactor.keyguardLongPressInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTouchHandlingInteractor import com.android.systemui.kosmos.Kosmos -val Kosmos.keyguardLongPressViewModel by +val Kosmos.keyguardTouchHandlingViewModel by Kosmos.Fixture { - KeyguardLongPressViewModel( - interactor = keyguardLongPressInteractor, + KeyguardTouchHandlingViewModel( + interactor = keyguardTouchHandlingInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt index 30a4f21b67f5..24e47b0af3fa 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt @@ -30,7 +30,7 @@ val Kosmos.lockscreenContentViewModel by clockInteractor = keyguardClockInteractor, interactor = keyguardBlueprintInteractor, authController = authController, - longPress = keyguardLongPressViewModel, + touchHandling = keyguardTouchHandlingViewModel, shadeInteractor = shadeInteractor, applicationScope = applicationCoroutineScope, unfoldTransitionInteractor = unfoldTransitionInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt index 419e7810b604..dceb8bff0ae7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt @@ -33,7 +33,6 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import javax.inject.Provider import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow var Kosmos.newFactoryTileMap by Kosmos.Fixture { emptyMap<String, Provider<QSTileViewModel>>() } @@ -50,7 +49,7 @@ val Kosmos.customTileViewModelFactory: QSTileViewModelFactory.Component by instanceIdSequenceFake.newInstanceId(), ) object : QSTileViewModel { - override val state: SharedFlow<QSTileState> = + override val state: StateFlow<QSTileState?> = MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {}) override val config: QSTileConfig = config override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt index dcfcce77942e..537be4fc2527 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt @@ -69,6 +69,7 @@ val Kosmos.qsQRCodeScannerViewModel by qsTileLogger, systemClock, testDispatcher, + testDispatcher, testScope.backgroundScope, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/PulsingGestureListenerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/PulsingGestureListenerKosmos.kt new file mode 100644 index 000000000000..4fc22289585f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/PulsingGestureListenerKosmos.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import android.hardware.display.ambientDisplayConfiguration +import com.android.systemui.classifier.falsingManager +import com.android.systemui.dock.dockManager +import com.android.systemui.dump.dumpManager +import com.android.systemui.keyguard.domain.interactor.dozeInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.settings.userTracker +import com.android.systemui.util.mockito.mock + +val Kosmos.pulsingGestureListener by Fixture { + PulsingGestureListener( + falsingManager = falsingManager, + dockManager = dockManager, + powerInteractor = powerInteractor, + ambientDisplayConfiguration = ambientDisplayConfiguration, + statusBarStateController = statusBarStateController, + shadeLogger = mock(), + dozeInteractor = dozeInteractor, + userTracker = userTracker, + tunerService = mock(), + dumpManager = dumpManager, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt index b85858d915b5..79b80bc71c58 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt @@ -27,7 +27,9 @@ import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.panelExpansionInteractor import com.android.systemui.shade.transition.ScrimShadeTransitionController +import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController import com.android.systemui.statusbar.policy.splitShadeStateController +import com.android.systemui.statusbar.pulseExpansionHandler import com.android.systemui.util.mockito.mock @Deprecated("ShadeExpansionStateManager is deprecated. Remove your dependency on it instead.") @@ -45,5 +47,7 @@ val Kosmos.shadeStartable by Fixture { sceneInteractorProvider = { sceneInteractor }, panelExpansionInteractorProvider = { panelExpansionInteractor }, shadeExpansionStateManager = shadeExpansionStateManager, + pulseExpansionHandler = pulseExpansionHandler, + nsslc = notificationStackScrollLayoutController, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt new file mode 100644 index 000000000000..989c3a5d6d05 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt @@ -0,0 +1,44 @@ +/* + * 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.shade.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor +import com.android.systemui.qs.footerActionsController +import com.android.systemui.qs.footerActionsViewModelFactory +import com.android.systemui.qs.ui.adapter.qsSceneAdapter +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor + +val Kosmos.shadeSceneViewModel: ShadeSceneViewModel by + Kosmos.Fixture { + ShadeSceneViewModel( + applicationScope = applicationCoroutineScope, + shadeHeaderViewModel = shadeHeaderViewModel, + qsSceneAdapter = qsSceneAdapter, + brightnessMirrorViewModel = brightnessMirrorViewModel, + mediaCarouselInteractor = mediaCarouselInteractor, + shadeInteractor = shadeInteractor, + footerActionsViewModelFactory = footerActionsViewModelFactory, + footerActionsController = footerActionsController, + sceneInteractor = sceneInteractor, + unfoldTransitionInteractor = unfoldTransitionInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt index f0eea386ecdb..a0e9303890ba 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt @@ -18,10 +18,10 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.dump.dumpManager import com.android.systemui.flags.featureFlagsClassic -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.ui.viewmodel.shadeSceneViewModel import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor val Kosmos.notificationsPlaceholderViewModel by Fixture { @@ -29,7 +29,7 @@ val Kosmos.notificationsPlaceholderViewModel by Fixture { dumpManager = dumpManager, interactor = notificationStackAppearanceInteractor, shadeInteractor = shadeInteractor, + shadeSceneViewModel = shadeSceneViewModel, featureFlags = featureFlagsClassic, - keyguardInteractor = keyguardInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt new file mode 100644 index 000000000000..8785256de452 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.content.applicationContext +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.statusbar.notificationShadeWindowController +import com.android.systemui.statusbar.policy.configurationController +import com.android.systemui.statusbar.policy.headsUpManager +import com.android.systemui.util.kotlin.JavaAdapter +import com.android.systemui.util.mockito.mock +import org.mockito.Mockito.mock + +var Kosmos.statusBarTouchableRegionManager by + Kosmos.Fixture { + StatusBarTouchableRegionManager( + applicationContext, + notificationShadeWindowController, + configurationController, + headsUpManager, + shadeInteractor, + { sceneInteractor }, + JavaAdapter(testScope.backgroundScope), + mock<UnlockedScreenOffAnimationController>(), + primaryBouncerInteractor, + alternateBouncerInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt index b2b19de654b6..e6b52f0c52e0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt @@ -20,6 +20,7 @@ import android.content.applicationContext import com.android.internal.logging.uiEventLogger import com.android.systemui.kosmos.Kosmos import com.android.systemui.volume.domain.interactor.audioVolumeInteractor +import com.android.systemui.volume.shared.volumePanelLogger import kotlinx.coroutines.CoroutineScope val Kosmos.audioStreamSliderViewModelFactory by @@ -36,6 +37,7 @@ val Kosmos.audioStreamSliderViewModelFactory by applicationContext, audioVolumeInteractor, uiEventLogger, + volumePanelLogger, ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/shared/VolumePanelLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/shared/VolumePanelLoggerKosmos.kt new file mode 100644 index 000000000000..3a7574d5deeb --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/shared/VolumePanelLoggerKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.shared + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.volume.panel.shared.VolumePanelLogger + +val Kosmos.volumePanelLogger by Kosmos.Fixture { VolumePanelLogger(logcatLogBuffer()) } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt index fec6ff17a608..0458f535c1eb 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt @@ -28,6 +28,9 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.atomic.AtomicInteger + +private const val INVALID_ROTATION = -1 /** * Allows to subscribe to rotation changes. Updates are provided for the display associated to @@ -45,7 +48,7 @@ constructor( private val listeners = CopyOnWriteArrayList<RotationListener>() private val displayListener = RotationDisplayListener() - private var lastRotation: Int? = null + private val lastRotation = AtomicInteger(INVALID_ROTATION) override fun addCallback(listener: RotationListener) { bgHandler.post { @@ -61,7 +64,7 @@ constructor( listeners -= listener if (listeners.isEmpty()) { unsubscribeToRotation() - lastRotation = null + lastRotation.set(INVALID_ROTATION) } } } @@ -100,9 +103,8 @@ constructor( if (displayId == display.displayId) { val currentRotation = display.rotation - if (lastRotation == null || lastRotation != currentRotation) { + if (lastRotation.compareAndSet(lastRotation.get(), currentRotation)) { listeners.forEach { it.onRotationChanged(currentRotation) } - lastRotation = currentRotation } } } finally { diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt index f3172ae3090d..bdc357789191 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -274,7 +274,9 @@ android.telephony.CellSignalStrength android.telephony.ModemActivityInfo android.telephony.ServiceState +android.os.connectivity.CellularBatteryStats android.os.connectivity.WifiActivityEnergyInfo +android.os.connectivity.WifiBatteryStats com.android.server.LocalServices diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index f9196f3e0e0e..d3efa21a2311 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -610,7 +610,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } } - void notifyAccessibilityButtonClicked(int displayId) { + void notifyMagnificationShortcutTriggered(int displayId) { if (mMagnificationGestureHandler.size() != 0) { final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId); if (handler != null) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 1dc3fb421545..36d97f659f6c 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2220,10 +2220,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private void sendAccessibilityButtonToInputFilter(int displayId) { + private void notifyMagnificationShortcutTriggered(int displayId) { synchronized (mLock) { if (mHasInputFilter && mInputFilter != null) { - mInputFilter.notifyAccessibilityButtonClicked(displayId); + mInputFilter.notifyMagnificationShortcutTriggered(displayId); } } } @@ -3898,7 +3898,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub .isActivated(displayId); logAccessibilityShortcutActivated(mContext, MAGNIFICATION_COMPONENT_NAME, shortcutType, enabled); - sendAccessibilityButtonToInputFilter(displayId); + notifyMagnificationShortcutTriggered(displayId); return; } final ComponentName targetComponentName = ComponentName.unflattenFromString(targetName); diff --git a/services/autofill/java/com/android/server/autofill/RequestId.java b/services/autofill/java/com/android/server/autofill/RequestId.java index 29ad786dbd4b..d8069a840156 100644 --- a/services/autofill/java/com/android/server/autofill/RequestId.java +++ b/services/autofill/java/com/android/server/autofill/RequestId.java @@ -16,8 +16,14 @@ package com.android.server.autofill; -import java.util.List; +import static com.android.server.autofill.Helper.sDebug; + +import android.util.Slog; +import android.util.SparseArray; + import java.util.concurrent.atomic.AtomicInteger; +import java.util.List; +import java.util.Random; // Helper class containing various methods to deal with FillRequest Ids. // For authentication flows, there needs to be a way to know whether to retrieve the Fill @@ -25,56 +31,97 @@ import java.util.concurrent.atomic.AtomicInteger; // way to achieve this is by assigning odd number request ids to secondary provider and // even numbers to primary provider. public class RequestId { + private AtomicInteger sIdCounter; + + // The minimum request id is 2 to avoid possible authentication issues. + static final int MIN_REQUEST_ID = 2; + // The maximum request id is 0x7FFF to make sure the 16th bit is 0. + // This is to make sure the authentication id is always positive. + static final int MAX_REQUEST_ID = 0x7FFF; // 32767 + + // The maximum start id is made small to best avoid wrapping around. + static final int MAX_START_ID = 1000; + // The magic number is used to determine if a wrap has happened. + // The underlying assumption of MAGIC_NUMBER is that there can't be as many as MAGIC_NUMBER + // of fill requests in one session. so there can't be as many as MAGIC_NUMBER of fill requests + // getting dropped. + static final int MAGIC_NUMBER = 5000; + + static final int MIN_PRIMARY_REQUEST_ID = 2; + static final int MAX_PRIMARY_REQUEST_ID = 0x7FFE; // 32766 + + static final int MIN_SECONDARY_REQUEST_ID = 3; + static final int MAX_SECONDARY_REQUEST_ID = 0x7FFF; // 32767 + + private static final String TAG = "RequestId"; + + // WARNING: This constructor should only be used for testing + RequestId(int startId) { + if (startId < MIN_REQUEST_ID || startId > MAX_REQUEST_ID) { + throw new IllegalArgumentException("startId must be between " + MIN_REQUEST_ID + + " and " + MAX_REQUEST_ID); + } + if (sDebug) { + Slog.d(TAG, "RequestId(int): startId= " + startId); + } + sIdCounter = new AtomicInteger(startId); + } - private AtomicInteger sIdCounter; - - // Mainly used for tests - RequestId(int start) { - sIdCounter = new AtomicInteger(start); - } - - public RequestId() { - this((int) (Math.floor(Math.random() * 0xFFFF))); - } - - public static int getLastRequestIdIndex(List<Integer> requestIds) { - int lastId = -1; - int indexOfBiggest = -1; - // Biggest number is usually the latest request, since IDs only increase - // The only exception is when the request ID wraps around back to 0 - for (int i = requestIds.size() - 1; i >= 0; i--) { - if (requestIds.get(i) > lastId) { - lastId = requestIds.get(i); - indexOfBiggest = i; - } + // WARNING: This get method should only be used for testing + int getRequestId() { + return sIdCounter.get(); } - // 0xFFFE + 2 == 0x1 (for secondary) - // 0xFFFD + 2 == 0x0 (for primary) - // Wrap has occurred - if (lastId >= 0xFFFD) { - // Calculate the biggest size possible - // If list only has one kind of request ids - we need to multiple by 2 - // (since they skip odd ints) - // Also subtract one from size because at least one integer exists pre-wrap - int calcSize = (requestIds.size()) * 2; - //Biggest possible id after wrapping - int biggestPossible = (lastId + calcSize) % 0xFFFF; - lastId = -1; - indexOfBiggest = -1; - for (int i = 0; i < requestIds.size(); i++) { - int currentId = requestIds.get(i); - if (currentId <= biggestPossible && currentId > lastId) { - lastId = currentId; - indexOfBiggest = i; + public RequestId() { + Random random = new Random(); + int low = MIN_REQUEST_ID; + int high = MAX_START_ID + 1; // nextInt is exclusive on upper limit + + // Generate a random start request id that >= MIN_REQUEST_ID and <= MAX_START_ID + int startId = random.nextInt(high - low) + low; + if (sDebug) { + Slog.d(TAG, "RequestId(): startId= " + startId); } - } + sIdCounter = new AtomicInteger(startId); } - return indexOfBiggest; - } + // Given a list of request ids, find the index of the last request id. + // Note: Since the request id wraps around, the largest request id may not be + // the latest request id. + // + // @param requestIds List of request ids in ascending order with at least one element. + // @return Index of the last request id. + public static int getLastRequestIdIndex(List<Integer> requestIds) { + // If there is only one request id, return index as 0. + if (requestIds.size() == 1) { + return 0; + } + + // We have to use a magical number to determine if a wrap has happened because + // the request id could be lost. The underlying assumption of MAGIC_NUMBER is that + // there can't be as many as MAGIC_NUMBER of fill requests in one session. + boolean wrapHasHappened = false; + int latestRequestIdIndex = -1; + + for (int i = 0; i < requestIds.size() - 1; i++) { + if (requestIds.get(i+1) - requestIds.get(i) > MAGIC_NUMBER) { + wrapHasHappened = true; + latestRequestIdIndex = i; + break; + } + } + + // If there was no wrap, the last request index is the last index. + if (!wrapHasHappened) { + latestRequestIdIndex = requestIds.size() - 1; + } + if (sDebug) { + Slog.d(TAG, "getLastRequestIdIndex(): latestRequestIdIndex = " + latestRequestIdIndex); + } + return latestRequestIdIndex; + } - public int nextId(boolean isSecondary) { + public int nextId(boolean isSecondary) { // For authentication flows, there needs to be a way to know whether to retrieve the Fill // Response from the primary provider or the secondary provider from the requestId. A simple // way to achieve this is by assigning odd number request ids to secondary provider and @@ -82,13 +129,20 @@ public class RequestId { int requestId; do { - requestId = sIdCounter.incrementAndGet() % 0xFFFF; + requestId = sIdCounter.incrementAndGet() % (MAX_REQUEST_ID + 1); + // Skip numbers smaller than MIN_REQUEST_ID to avoid possible authentication issue + if (requestId < MIN_REQUEST_ID) { + requestId = MIN_REQUEST_ID; + } sIdCounter.set(requestId); } while (isSecondaryProvider(requestId) != isSecondary); + if (sDebug) { + Slog.d(TAG, "nextId(): requestId = " + requestId); + } return requestId; - } + } - public static boolean isSecondaryProvider(int requestId) { - return requestId % 2 == 1; - } + public static boolean isSecondaryProvider(int requestId) { + return requestId % 2 == 1; + } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 494e956c413f..c6ddc16211bc 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -6902,17 +6902,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING; } + // Return latest response index in mResponses SparseArray. @GuardedBy("mLock") private int getLastResponseIndexLocked() { - if (mResponses != null) { - List<Integer> requestIdList = new ArrayList<>(); - final int responseCount = mResponses.size(); - for (int i = 0; i < responseCount; i++) { - requestIdList.add(mResponses.keyAt(i)); - } - return mRequestId.getLastRequestIdIndex(requestIdList); + if (mResponses == null || mResponses.size() == 0) { + return -1; + } + List<Integer> requestIdList = new ArrayList<>(); + final int responseCount = mResponses.size(); + for (int i = 0; i < responseCount; i++) { + requestIdList.add(mResponses.keyAt(i)); } - return -1; + return mRequestId.getLastRequestIdIndex(requestIdList); } private LogMaker newLogMaker(int category) { diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index afeafa4b6373..988a2132455b 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -87,15 +87,6 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController void onSecureWindowShown(int displayId, int uid); } - /** - * For communicating when activities are blocked from entering PIP on the display by this - * policy controller. - */ - public interface PipBlockedCallback { - /** Called when an activity is blocked from entering PIP. */ - void onEnteringPipBlocked(int uid); - } - /** Interface to listen for interception of intents. */ public interface IntentListenerCallback { /** Returns true when an intent should be intercepted */ @@ -136,7 +127,6 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController @GuardedBy("mGenericWindowPolicyControllerLock") private final ArraySet<Integer> mRunningUids = new ArraySet<>(); @Nullable private final ActivityListener mActivityListener; - @Nullable private final PipBlockedCallback mPipBlockedCallback; @Nullable private final IntentListenerCallback mIntentListenerCallback; private final Handler mHandler = new Handler(Looper.getMainLooper()); @NonNull @@ -190,7 +180,6 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController @NonNull Set<ComponentName> crossTaskNavigationExemptions, @Nullable ComponentName permissionDialogComponent, @Nullable ActivityListener activityListener, - @Nullable PipBlockedCallback pipBlockedCallback, @Nullable ActivityBlockedCallback activityBlockedCallback, @Nullable SecureWindowCallback secureWindowCallback, @Nullable IntentListenerCallback intentListenerCallback, @@ -208,7 +197,6 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController mActivityBlockedCallback = activityBlockedCallback; setInterestedWindowFlags(windowFlags, systemWindowFlags); mActivityListener = activityListener; - mPipBlockedCallback = pipBlockedCallback; mSecureWindowCallback = secureWindowCallback; mIntentListenerCallback = intentListenerCallback; mDisplayCategories = displayCategories; @@ -346,6 +334,10 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController } final UserHandle activityUser = UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid); + if (!activityUser.isSystem() && !mAllowedUsers.contains(activityUser)) { + logActivityLaunchBlocked("Activity launch disallowed from user " + activityUser); + return false; + } final ComponentName activityComponent = activityInfo.getComponentName(); if (BLOCKED_APP_STREAMING_COMPONENT.equals(activityComponent) && activityUser.isSystem()) { // The error dialog alerting users that streaming is blocked is always allowed. @@ -464,18 +456,6 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController return mShowTasksInHostDeviceRecents; } } - - @Override - public boolean isEnteringPipAllowed(int uid) { - if (super.isEnteringPipAllowed(uid)) { - return true; - } - if (mPipBlockedCallback != null) { - mHandler.post(() -> mPipBlockedCallback.onEnteringPipBlocked(uid)); - } - return false; - } - @Override public @Nullable ComponentName getCustomHomeComponent() { return mCustomHomeComponent; @@ -512,7 +492,6 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController "virtual_devices.value_activity_blocked_count", mAttributionSource.getUid()); } - } private static boolean isAllowedByPolicy(boolean allowedByDefault, diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index a72259e70efb..657e261a345f 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -24,6 +24,7 @@ import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAUL import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO; +import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD; import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS; @@ -186,6 +187,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @GuardedBy("mVirtualDeviceLock") private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>(); private IVirtualDeviceActivityListener mActivityListener; + private ActivityListener mActivityListenerAdapter = null; private IVirtualDeviceSoundEffectListener mSoundEffectListener; private final DisplayManagerGlobal mDisplayManager; private final DisplayManagerInternal mDisplayManagerInternal; @@ -239,6 +241,16 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e); } } + + @Override + public void onActivityLaunchBlocked(int displayId, + @NonNull ComponentName componentName, @UserIdInt int userId) { + try { + mActivityListener.onActivityLaunchBlocked(displayId, componentName, userId); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e); + } + } }; } @@ -709,6 +721,13 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } break; + case POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR: + if (android.companion.virtualdevice.flags.Flags.activityControlApi()) { + synchronized (mVirtualDeviceLock) { + mDevicePolicies.put(policyType, devicePolicy); + } + } + break; default: throw new IllegalArgumentException("Device policy " + policyType + " cannot be changed at runtime. "); @@ -1209,6 +1228,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub final ComponentName homeComponent = Flags.vdmCustomHome() ? mParams.getHomeComponent() : null; + if (mActivityListenerAdapter == null) { + mActivityListenerAdapter = createListenerAdapter(); + } + final GenericWindowPolicyController gwpc = new GenericWindowPolicyController( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, @@ -1221,8 +1244,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub ? mParams.getBlockedCrossTaskNavigations() : mParams.getAllowedCrossTaskNavigations(), mPermissionDialogComponent, - createListenerAdapter(), - this::onEnteringPipBlocked, + mActivityListenerAdapter, this::onActivityBlocked, this::onSecureWindowShown, this::shouldInterceptIntent, @@ -1307,10 +1329,36 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) private void onActivityBlocked(int displayId, ActivityInfo activityInfo) { Intent intent = BlockedAppStreamingActivity.createIntent(activityInfo, getDisplayName()); - mContext.startActivityAsUser( - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK), - ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(), - UserHandle.SYSTEM); + if (shouldShowBlockedActivityDialog( + activityInfo.getComponentName(), intent.getComponent())) { + mContext.startActivityAsUser( + intent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK), + ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(), + UserHandle.SYSTEM); + } + + if (android.companion.virtualdevice.flags.Flags.activityControlApi()) { + mActivityListenerAdapter.onActivityLaunchBlocked( + displayId, + activityInfo.getComponentName(), + UserHandle.getUserHandleForUid( + activityInfo.applicationInfo.uid).getIdentifier()); + } + } + + private boolean shouldShowBlockedActivityDialog(ComponentName blockedComponent, + ComponentName blockedAppStreamingActivityComponent) { + if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) { + return true; + } + if (Objects.equals(blockedComponent, blockedAppStreamingActivityComponent)) { + // Do not show the dialog if it was blocked for some reason already to avoid + // infinite blocking loop. + return false; + } + // Do not show the dialog if disabled by policy. + return getDevicePolicy(POLICY_TYPE_BLOCKED_ACTIVITY_BEHAVIOR) == DEVICE_POLICY_DEFAULT; } private void onSecureWindowShown(int displayId, int uid) { @@ -1510,12 +1558,6 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return mInputController.getInputDeviceDescriptors().values().stream().anyMatch( inputDeviceDescriptor -> inputDeviceDescriptor.getInputDeviceId() == inputDeviceId); } - - void onEnteringPipBlocked(int uid) { - // Do nothing. ActivityRecord#checkEnterPictureInPictureState logs that the display does not - // support PiP. - } - void playSoundEffect(int effectType) { try { mSoundEffectListener.onPlaySoundEffect(effectType); diff --git a/services/core/Android.bp b/services/core/Android.bp index d9962c7b02d5..1cd20ed0f7cd 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -47,7 +47,7 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) transform-protolog-calls " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--protolog-class com.android.internal.protolog.ProtoLog " + "--loggroups-class com.android.internal.protolog.ProtoLogGroup " + "--loggroups-jar $(location :protolog-groups) " + "--viewer-config-file-path /etc/core.protolog.pb " + @@ -66,7 +66,7 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) generate-viewer-config " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--protolog-class com.android.internal.protolog.ProtoLog " + "--loggroups-class com.android.internal.protolog.ProtoLogGroup " + "--loggroups-jar $(location :protolog-groups) " + "--viewer-config-type json " + @@ -83,7 +83,7 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) generate-viewer-config " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--protolog-class com.android.internal.protolog.ProtoLog " + "--loggroups-class com.android.internal.protolog.ProtoLogGroup " + "--loggroups-jar $(location :protolog-groups) " + "--viewer-config-type proto " + diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java index 3d3535d2dbd2..73d8384395d2 100644 --- a/services/core/java/com/android/server/SystemServerInitThreadPool.java +++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java @@ -197,8 +197,8 @@ public final class SystemServerInitThreadPool implements Dumpable { /* processCpuTracker= */null, /* lastPids= */null, CompletableFuture.completedFuture(Watchdog.getInterestingNativePids()), /* logExceptionCreatingFile= */null, /* subject= */null, - /* criticalEventSection= */null, Runnable::run, - /* latencyTracker= */null); + /* criticalEventSection= */null, /* extraHeaders= */ null, + Runnable::run, /* latencyTracker= */null); } @Override diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 72a55dbea481..21947bac137b 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -74,6 +74,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -991,6 +992,9 @@ public class Watchdog implements Dumpable { FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject); } + final LinkedHashMap headersMap = + com.android.server.am.Flags.enableDropboxWatchdogHeaders() + ? new LinkedHashMap<>(Collections.singletonMap("Watchdog-Type", dropboxTag)) : null; long anrTime = SystemClock.uptimeMillis(); StringBuilder report = new StringBuilder(); report.append(ResourcePressureUtil.currentPsiState()); @@ -998,8 +1002,9 @@ public class Watchdog implements Dumpable { StringWriter tracesFileException = new StringWriter(); final File stack = StackTracesDumpHelper.dumpStackTraces( pids, processCpuTracker, new SparseBooleanArray(), - CompletableFuture.completedFuture(getInterestingNativePids()), tracesFileException, - subject, criticalEvents, Runnable::run, /* latencyTracker= */null); + CompletableFuture.completedFuture(getInterestingNativePids()), + tracesFileException, subject, criticalEvents, headersMap, + Runnable::run, /* latencyTracker= */null); // Give some extra time to make sure the stack traces get written. // The system's been hanging for a whlie, another second or two won't hurt much. SystemClock.sleep(5000); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b23f5f23f647..25fb729e7e7c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -429,7 +429,7 @@ import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.policy.AttributeCache; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FrameworkStatsLog; @@ -5481,8 +5481,13 @@ public class ActivityManagerService extends IActivityManager.Stub private boolean isHomeLaunchDelayable() { // This feature is disabled on Auto since it seems to add an unacceptably long boot delay // without even solving the underlying issue (it merely hits the timeout). - return enableHomeDelay() && - !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + // This feature is disabled on TV since the ThemeOverlayController is currently not present + // and therefore we do not want to wait unnecessarily. + // This feature is currently disabled in WearOS to avoid extreme boot regressions + return enableHomeDelay() + && !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + && !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) + && !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); } final void ensureBootCompleted() { diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 0c14a1cf84cd..00183ac0350b 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -848,6 +848,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } private void syncStats(String reason, int flags) { + mStats.collectPowerStatsSamples(); awaitUninterruptibly(mWorker.scheduleSync(reason, flags)); } diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index d4d49654ad29..ba4b71cd7540 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -60,7 +60,6 @@ import com.android.server.ResourcePressureUtil; import com.android.server.criticalevents.CriticalEventLog; import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot; import com.android.server.wm.WindowProcessController; -import com.android.server.utils.AnrTimer; import java.io.File; import java.io.PrintWriter; @@ -69,6 +68,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -429,7 +429,7 @@ class ProcessErrorStateRecord { } } // Build memory headers for the ANRing process. - String memoryHeaders = buildMemoryHeadersFor(pid); + LinkedHashMap<String, String> memoryHeaders = buildMemoryHeadersFor(pid); // Get critical event log before logging the ANR so that it doesn't occur in the log. latencyTracker.criticalEventLogStarted(); @@ -753,7 +753,7 @@ class ProcessErrorStateRecord { resolver.getUserId()) != 0; } - private @Nullable String buildMemoryHeadersFor(int pid) { + private @Nullable LinkedHashMap<String, String> buildMemoryHeadersFor(int pid) { if (pid <= 0) { Slog.i(TAG, "Memory header requested with invalid pid: " + pid); return null; @@ -764,15 +764,13 @@ class ProcessErrorStateRecord { return null; } - StringBuilder memoryHeaders = new StringBuilder(); - memoryHeaders.append("RssHwmKb: ") - .append(snapshot.rssHighWaterMarkInKilobytes) - .append("\n"); - memoryHeaders.append("RssKb: ").append(snapshot.rssInKilobytes).append("\n"); - memoryHeaders.append("RssAnonKb: ").append(snapshot.anonRssInKilobytes).append("\n"); - memoryHeaders.append("RssShmemKb: ").append(snapshot.rssShmemKilobytes).append("\n"); - memoryHeaders.append("VmSwapKb: ").append(snapshot.swapInKilobytes).append("\n"); - return memoryHeaders.toString(); + LinkedHashMap<String, String> memoryHeaders = new LinkedHashMap<>(); + memoryHeaders.put("RssHwmKb", Integer.toString(snapshot.rssHighWaterMarkInKilobytes)); + memoryHeaders.put("RssKb", Integer.toString(snapshot.rssInKilobytes)); + memoryHeaders.put("RssAnonKb", Integer.toString(snapshot.anonRssInKilobytes)); + memoryHeaders.put("RssShmemKb", Integer.toString(snapshot.rssShmemKilobytes)); + memoryHeaders.put("VmSwapKb", Integer.toString(snapshot.swapInKilobytes)); + return memoryHeaders; } /** * Unless configured otherwise, swallow ANRs in background processes & kill the process. diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index c0947247de4b..f4b1229696f5 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -855,8 +855,8 @@ public final class ProcessList { Slog.i(TAG, "Failed to connect to lmkd, retry after " + LMKD_RECONNECT_DELAY_MS + " ms"); // retry after LMKD_RECONNECT_DELAY_MS - sKillHandler.sendMessageDelayed(sKillHandler.obtainMessage( - KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS); + sendMessageDelayed(obtainMessage( + LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS); } break; default: diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java index c1374e1b5a05..2021ba4f3b5d 100644 --- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java +++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java @@ -47,6 +47,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -100,15 +102,17 @@ public class StackTracesDumpHelper { /** * @param subject the subject of the dumped traces * @param criticalEventSection the critical event log, passed as a string + * @param extraHeaders Optional, Extra headers added by the dumping method */ public static File dumpStackTraces(ArrayList<Integer> firstPids, ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids, Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile, - String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor, + String subject, String criticalEventSection, + LinkedHashMap<String, String> extraHeaders, @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) { return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture, logExceptionCreatingFile, null, subject, criticalEventSection, - /* memoryHeaders= */ null, auxiliaryTaskExecutor, null, latencyTracker); + extraHeaders, auxiliaryTaskExecutor, null, latencyTracker); } /** @@ -119,7 +123,7 @@ public class StackTracesDumpHelper { ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids, Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile, AtomicLong firstPidEndOffset, String subject, String criticalEventSection, - String memoryHeaders, @NonNull Executor auxiliaryTaskExecutor, + LinkedHashMap<String, String> extraHeaders, @NonNull Executor auxiliaryTaskExecutor, Future<File> firstPidFilePromise, AnrLatencyTracker latencyTracker) { try { @@ -159,11 +163,12 @@ public class StackTracesDumpHelper { } return null; } + boolean extraHeadersExist = extraHeaders != null && !extraHeaders.isEmpty(); - if (subject != null || criticalEventSection != null || memoryHeaders != null) { + if (subject != null || criticalEventSection != null || extraHeadersExist) { appendtoANRFile(tracesFile.getAbsolutePath(), (subject != null ? "Subject: " + subject + "\n" : "") - + (memoryHeaders != null ? memoryHeaders + "\n\n" : "") + + (extraHeadersExist ? stringifyHeaders(extraHeaders) + "\n\n" : "") + (criticalEventSection != null ? criticalEventSection : "")); } @@ -614,4 +619,15 @@ public class StackTracesDumpHelper { return pids; } + private static String stringifyHeaders(@NonNull LinkedHashMap<String, String> headers) { + StringBuilder headersString = new StringBuilder(); + for (Map.Entry<String, String> entry : headers.entrySet()) { + headersString.append(entry.getKey()) + .append(": ") + .append(entry.getValue()) + .append("\n"); + } + return headersString.toString(); + } + } diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 0dfa3302adfd..9b380ff12e2d 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -167,4 +167,12 @@ flag { description: "Allow logcat collection on synchronous dropbox collection" bug: "324222683" is_fixed_read_only: true -}
\ No newline at end of file +} + +flag { + name: "enable_dropbox_watchdog_headers" + namespace: "dropbox" + description: "Add watchdog-specific dropbox headers" + bug: "330682397" + is_fixed_read_only: true +} diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 27fda15d5a05..cc4f7d9b2ddb 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -51,6 +51,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; +import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; @@ -374,6 +375,16 @@ public class AudioDeviceBroker { deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource); } + /** + * Indicates if a Bluetooth SCO activation request owner is controlling + * the SCO audio state itself or not. + * @param uid the UI of the SOC request owner app + * @return true if we should control SCO audio state, false otherwise + */ + private boolean shouldStartScoForUid(int uid) { + return !(uid == Process.BLUETOOTH_UID || uid == Process.PHONE_UID); + } + @GuardedBy("mDeviceStateLock") /*package*/ void setCommunicationRouteForClient( IBinder cb, int uid, AudioDeviceAttributes device, @@ -388,7 +399,7 @@ public class AudioDeviceBroker { + " device: " + device + " isPrivileged: " + isPrivileged + " from API: " + eventSource)).printLog(TAG)); - final boolean wasBtScoRequested = isBluetoothScoRequested(); + final int previousBtScoRequesterUid = bluetoothScoRequestOwnerUid(); CommunicationRouteClient client; // Save previous client route in case of failure to start BT SCO audio @@ -412,8 +423,40 @@ public class AudioDeviceBroker { if (client == null) { return; } - if (!mScoManagedByAudio) { - boolean isBtScoRequested = isBluetoothScoRequested(); + final int btScoRequesterUid = bluetoothScoRequestOwnerUid(); + final boolean isBtScoRequested = btScoRequesterUid != -1; + final boolean wasBtScoRequested = previousBtScoRequesterUid != -1; + + if (mScoManagedByAudio) { + if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive() + || !mBtHelper.isBluetoothScoRequestedInternally())) { + boolean scoStarted = false; + if (shouldStartScoForUid(btScoRequesterUid)) { + scoStarted = mBtHelper.startBluetoothSco(scoAudioMode, eventSource); + if (!scoStarted) { + Log.w(TAG, "setCommunicationRouteForClient: " + + "failure to start BT SCO for uid: " + uid); + // clean up or restore previous client selection + if (prevClientDevice != null) { + addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged); + } else { + removeCommunicationRouteClient(cb, true); + } + postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + } else { + scoStarted = true; + } + if (scoStarted) { + setBluetoothScoOn(true, "setCommunicationRouteForClient"); + } + } else if (!isBtScoRequested && wasBtScoRequested) { + if (shouldStartScoForUid(previousBtScoRequesterUid)) { + mBtHelper.stopBluetoothSco(eventSource); + } + setBluetoothScoOn(false, "setCommunicationRouteForClient"); + } + } else { if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive() || !mBtHelper.isBluetoothScoRequestedInternally())) { if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) { @@ -575,12 +618,12 @@ public class AudioDeviceBroker { @GuardedBy("mDeviceStateLock") /*package*/ void updateCommunicationRouteClientState( CommunicationRouteClient client, boolean wasActive) { - boolean wasBtScoRequested = isBluetoothScoRequested(); + int btScoRequesterUid = bluetoothScoRequestOwnerUid(); client.setPlaybackActive(mAudioService.isPlaybackActiveForUid(client.getUid())); client.setRecordingActive(mAudioService.isRecordingActiveForUid(client.getUid())); if (wasActive != client.isActive()) { postUpdateCommunicationRouteClient( - wasBtScoRequested, "updateCommunicationRouteClientState"); + btScoRequesterUid, "updateCommunicationRouteClientState"); } } @@ -763,6 +806,22 @@ public class AudioDeviceBroker { } /** + * Helper method on top of isBluetoothScoRequested() returning the UID of the + * BT SCO route request owner of -1 if SCO is not requested. + * @return the UID of the BT SCO route request owner of -1 if SCO is not requested. + */ + @GuardedBy("mDeviceStateLock") + /*package*/ int bluetoothScoRequestOwnerUid() { + if (!isBluetoothScoRequested()) { + return -1; + } + CommunicationRouteClient crc = topCommunicationRouteClient(); + if (crc == null) { + return -1; + } + return crc.getUid(); + } + /** * Helper method on top of isDeviceRequestedForCommunication() indicating if * Bluetooth LE Audio communication device is currently requested or not. * @return true if Bluetooth LE Audio device is requested, false otherwise. @@ -1148,15 +1207,18 @@ public class AudioDeviceBroker { } } + @GuardedBy("mDeviceStateLock") /*package*/ void setBluetoothScoOn(boolean on, String eventSource) { synchronized (mBluetoothAudioStateLock) { - boolean isBtScoRequested = isBluetoothScoRequested(); + int btScoRequesterUId = bluetoothScoRequestOwnerUid(); Log.i(TAG, "setBluetoothScoOn: " + on + ", mBluetoothScoOn: " - + mBluetoothScoOn + ", isBtScoRequested: " + isBtScoRequested + + mBluetoothScoOn + ", btScoRequesterUId: " + btScoRequesterUId + ", from: " + eventSource); mBluetoothScoOn = on; updateAudioHalBluetoothState(); - postUpdateCommunicationRouteClient(isBtScoRequested, eventSource); + if (!mScoManagedByAudio) { + postUpdateCommunicationRouteClient(btScoRequesterUId, eventSource); + } } } @@ -1510,9 +1572,9 @@ public class AudioDeviceBroker { } /*package*/ void postUpdateCommunicationRouteClient( - boolean wasBtScoRequested, String eventSource) { + int btScoRequesterUid, String eventSource) { sendILMsgNoDelay(MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE, - wasBtScoRequested ? 1 : 0, eventSource); + btScoRequesterUid, eventSource); } /*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) { @@ -1865,7 +1927,7 @@ public class AudioDeviceBroker { || btInfo.mProfile == BluetoothProfile.HEARING_AID || (mScoManagedByAudio && btInfo.mProfile == BluetoothProfile.HEADSET)) { - onUpdateCommunicationRouteClient(isBluetoothScoRequested(), + onUpdateCommunicationRouteClient(bluetoothScoRequestOwnerUid(), "setBluetoothActiveDevice"); } } @@ -1927,11 +1989,11 @@ public class AudioDeviceBroker { case MSG_I_SET_MODE_OWNER: synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { - boolean wasBtScoRequested = isBluetoothScoRequested(); + int btScoRequesterUid = bluetoothScoRequestOwnerUid(); mAudioModeOwner = (AudioModeInfo) msg.obj; if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) { onUpdateCommunicationRouteClient( - wasBtScoRequested, "setNewModeOwner"); + btScoRequesterUid, "setNewModeOwner"); } } } @@ -1958,7 +2020,7 @@ public class AudioDeviceBroker { case MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT: synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { - onUpdateCommunicationRouteClient(msg.arg1 == 1, (String) msg.obj); + onUpdateCommunicationRouteClient(msg.arg1, (String) msg.obj); } } break; @@ -2457,7 +2519,7 @@ public class AudioDeviceBroker { @Nullable private AudioDeviceAttributes preferredCommunicationDevice() { boolean btSCoOn = mBtHelper.isBluetoothScoOn(); synchronized (mBluetoothAudioStateLock) { - btSCoOn = btSCoOn && mBluetoothScoOn; + btSCoOn = (btSCoOn || mScoManagedByAudio) && mBluetoothScoOn; } if (btSCoOn) { @@ -2522,18 +2584,28 @@ public class AudioDeviceBroker { */ // @GuardedBy("mSetModeLock") @GuardedBy("mDeviceStateLock") - private void onUpdateCommunicationRouteClient(boolean wasBtScoRequested, String eventSource) { + private void onUpdateCommunicationRouteClient( + int previousBtScoRequesterUid, String eventSource) { CommunicationRouteClient crc = topCommunicationRouteClient(); if (AudioService.DEBUG_COMM_RTE) { Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " + crc - + " wasBtScoRequested: " + wasBtScoRequested + " eventSource: " + eventSource); + + " previousBtScoRequesterUid: " + previousBtScoRequesterUid + + " eventSource: " + eventSource); } if (crc != null) { setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(), BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource); } else { - if (!mScoManagedByAudio && !isBluetoothScoRequested() && wasBtScoRequested) { - mBtHelper.stopBluetoothSco(eventSource); + boolean wasScoRequested = previousBtScoRequesterUid != -1; + if (!isBluetoothScoRequested() && wasScoRequested) { + if (mScoManagedByAudio) { + if (shouldStartScoForUid(previousBtScoRequesterUid)) { + mBtHelper.stopBluetoothSco(eventSource); + } + setBluetoothScoOn(false, eventSource); + } else { + mBtHelper.stopBluetoothSco(eventSource); + } } updateCommunicationRoute(eventSource); } @@ -2793,12 +2865,13 @@ public class AudioDeviceBroker { return mDeviceInventory.getImmutableDeviceInventory(); } - void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) { - mDeviceInventory.addOrUpdateDeviceSAStateInInventory(deviceState); + void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState, boolean syncInventory) { + mDeviceInventory.addOrUpdateDeviceSAStateInInventory(deviceState, syncInventory); } - void addOrUpdateBtAudioDeviceCategoryInInventory(AdiDeviceState deviceState) { - mDeviceInventory.addOrUpdateAudioDeviceCategoryInInventory(deviceState); + void addOrUpdateBtAudioDeviceCategoryInInventory( + AdiDeviceState deviceState, boolean syncInventory) { + mDeviceInventory.addOrUpdateAudioDeviceCategoryInInventory(deviceState, syncInventory); } @Nullable diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index ba7aee05159a..2986340c684d 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -135,9 +135,10 @@ public class AudioDeviceInventory { * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. * @param deviceState the device to update */ - void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) { + void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState, boolean syncInventory) { synchronized (mDeviceInventoryLock) { - mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> { + mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, + (oldState, newState) -> { oldState.setHasHeadTracker(newState.hasHeadTracker()); oldState.setHeadTrackerEnabled(newState.isHeadTrackerEnabled()); oldState.setSAEnabled(newState.isSAEnabled()); @@ -145,7 +146,9 @@ public class AudioDeviceInventory { }); checkDeviceInventorySize_l(); } - mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); + if (syncInventory) { + mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); + } } /** @@ -196,7 +199,8 @@ public class AudioDeviceInventory { * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. * @param deviceState the device to update */ - void addOrUpdateAudioDeviceCategoryInInventory(AdiDeviceState deviceState) { + void addOrUpdateAudioDeviceCategoryInInventory( + AdiDeviceState deviceState, boolean syncInventory) { AtomicBoolean updatedCategory = new AtomicBoolean(false); synchronized (mDeviceInventoryLock) { if (automaticBtDeviceType()) { @@ -218,7 +222,9 @@ public class AudioDeviceInventory { if (updatedCategory.get()) { mDeviceBroker.postUpdatedAdiDeviceState(deviceState, false /*initSA*/); } - mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); + if (syncInventory) { + mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); + } } void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address, @@ -235,14 +241,14 @@ public class AudioDeviceInventory { boolean bleCategoryFound = false; AdiDeviceState deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLE_HEADSET); if (deviceState != null) { - addOrUpdateAudioDeviceCategoryInInventory(deviceState); + addOrUpdateAudioDeviceCategoryInInventory(deviceState, true /*syncInventory*/); btCategory = deviceState.getAudioDeviceCategory(); bleCategoryFound = true; } deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLUETOOTH_A2DP); if (deviceState != null) { - addOrUpdateAudioDeviceCategoryInInventory(deviceState); + addOrUpdateAudioDeviceCategoryInInventory(deviceState, true /*syncInventory*/); int a2dpCategory = deviceState.getAudioDeviceCategory(); if (bleCategoryFound && a2dpCategory != btCategory) { Log.w(TAG, "Found different audio device category for A2DP and BLE profiles with " @@ -269,23 +275,43 @@ public class AudioDeviceInventory { } /** - * synchronize AdiDeviceState for LE devices in the same group + * Synchronize AdiDeviceState for LE devices in the same group + * or BT classic devices with the same address. + * @param updatedDevice the device state to synchronize or null. + * Called with null once after the device inventory and spatializer helper + * have been initialized to resync all devices. */ void onSynchronizeAdiDevicesInInventory(AdiDeviceState updatedDevice) { synchronized (mDevicesLock) { synchronized (mDeviceInventoryLock) { - boolean found = false; - found |= synchronizeBleDeviceInInventory(updatedDevice); - if (automaticBtDeviceType()) { - found |= synchronizeDeviceProfilesInInventory(updatedDevice); - } - if (found) { - mDeviceBroker.postPersistAudioDeviceSettings(); + if (updatedDevice != null) { + onSynchronizeAdiDeviceInInventory_l(updatedDevice); + } else { + for (AdiDeviceState ads : mDeviceInventory.values()) { + onSynchronizeAdiDeviceInInventory_l(ads); + } } } } } + /** + * Synchronize AdiDeviceState for LE devices in the same group + * or BT classic devices with the same address. + * @param updatedDevice the device state to synchronize. + */ + @GuardedBy({"mDevicesLock", "mDeviceInventoryLock"}) + void onSynchronizeAdiDeviceInInventory_l(AdiDeviceState updatedDevice) { + boolean found = false; + found |= synchronizeBleDeviceInInventory(updatedDevice); + if (automaticBtDeviceType()) { + found |= synchronizeDeviceProfilesInInventory(updatedDevice); + } + if (found) { + mDeviceBroker.postPersistAudioDeviceSettings(); + } + } + @GuardedBy("mDeviceInventoryLock") private void checkDeviceInventorySize_l() { if (mDeviceInventory.size() > MAX_DEVICE_INVENTORY_ENTRIES) { @@ -595,6 +621,9 @@ public class AudioDeviceInventory { mDeviceName = TextUtils.emptyIfNull(deviceName); mDeviceAddress = TextUtils.emptyIfNull(address); mDeviceIdentityAddress = TextUtils.emptyIfNull(identityAddress); + if (mDeviceIdentityAddress.isEmpty()) { + mDeviceIdentityAddress = mDeviceAddress; + } mDeviceCodecFormat = codecFormat; mGroupId = groupId; mPeerDeviceAddress = TextUtils.emptyIfNull(peerAddress); @@ -2499,7 +2528,7 @@ public class AudioDeviceInventory { mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name); - final int res = AudioSystem.setDeviceConnectionState(ada, + final int res = mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE, codec); if (res != AudioSystem.AUDIO_STATUS_OK) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( @@ -2546,7 +2575,7 @@ public class AudioDeviceInventory { AudioDeviceAttributes ada = null; if (device != AudioSystem.DEVICE_NONE) { ada = new AudioDeviceAttributes(device, address); - final int res = AudioSystem.setDeviceConnectionState(ada, + final int res = mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_UNAVAILABLE, codec); @@ -2951,8 +2980,8 @@ public class AudioDeviceInventory { // Note if the device is not compatible with spatialization mode or the device // type is not canonical, it will be ignored in {@link SpatializerHelper}. if (devState != null) { - addOrUpdateDeviceSAStateInInventory(devState); - addOrUpdateAudioDeviceCategoryInInventory(devState); + addOrUpdateDeviceSAStateInInventory(devState, false /*syncInventory*/); + addOrUpdateAudioDeviceCategoryInInventory(devState, false /*syncInventory*/); } } } diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java index 14eae8da0417..c5180afcce7d 100644 --- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java +++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java @@ -36,6 +36,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.ArraySet; import android.util.IntArray; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.media.permission.INativePermissionController; @@ -61,6 +62,9 @@ public class AudioServerPermissionProvider { static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE]; + static final byte[] HDS_PERMS = new byte[] {PermissionEnum.CAPTURE_AUDIO_HOTWORD, + PermissionEnum.CAPTURE_AUDIO_OUTPUT, PermissionEnum.RECORD_AUDIO}; + static { MONITORED_PERMS[PermissionEnum.RECORD_AUDIO] = RECORD_AUDIO; MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_ROUTING] = MODIFY_AUDIO_ROUTING; @@ -88,6 +92,7 @@ public class AudioServerPermissionProvider { @GuardedBy("mLock") private final Map<Integer, Set<String>> mPackageMap; + // Values are sorted @GuardedBy("mLock") private final int[][] mPermMap = new int[PermissionEnum.ENUM_SIZE][]; @@ -95,6 +100,9 @@ public class AudioServerPermissionProvider { @GuardedBy("mLock") private boolean mIsUpdateDeferred = true; + @GuardedBy("mLock") + private int mHdsUid = -1; + /** * @param appInfos - PackageState for all apps on the device, used to populate init state */ @@ -124,7 +132,7 @@ public class AudioServerPermissionProvider { try { for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { if (mIsUpdateDeferred) { - mPermMap[i] = getUidsHoldingPerm(MONITORED_PERMS[i]); + mPermMap[i] = getUidsHoldingPerm(i); } mDest.populatePermissionState(i, mPermMap[i]); } @@ -184,7 +192,7 @@ public class AudioServerPermissionProvider { } try { for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) { - var newPerms = getUidsHoldingPerm(MONITORED_PERMS[i]); + var newPerms = getUidsHoldingPerm(i); if (!Arrays.equals(newPerms, mPermMap[i])) { mPermMap[i] = newPerms; mDest.populatePermissionState(i, newPerms); @@ -199,6 +207,77 @@ public class AudioServerPermissionProvider { } } + public void setIsolatedServiceUid(int uid, int owningUid) { + synchronized (mLock) { + if (mHdsUid == uid) return; + var packageNameSet = mPackageMap.get(owningUid); + if (packageNameSet == null) return; + var packageName = packageNameSet.iterator().next(); + onModifyPackageState(uid, packageName, /* isRemove= */ false); + // permissions + mHdsUid = uid; + if (mDest == null) { + mIsUpdateDeferred = true; + return; + } + try { + for (byte perm : HDS_PERMS) { + int[] newPerms = new int[mPermMap[perm].length + 1]; + System.arraycopy(mPermMap[perm], 0, newPerms, 0, mPermMap[perm].length); + newPerms[newPerms.length - 1] = mHdsUid; + Arrays.sort(newPerms); + mPermMap[perm] = newPerms; + mDest.populatePermissionState(perm, newPerms); + } + } catch (RemoteException e) { + // We will re-init the state when the service comes back up + mDest = null; + // We didn't necessarily finish + mIsUpdateDeferred = true; + } + } + } + + public void clearIsolatedServiceUid(int uid) { + synchronized (mLock) { + if (mHdsUid != uid) return; + var packageNameSet = mPackageMap.get(uid); + if (packageNameSet == null) return; + var packageName = packageNameSet.iterator().next(); + onModifyPackageState(uid, packageName, /* isRemove= */ true); + // permissions + if (mDest == null) { + mIsUpdateDeferred = true; + return; + } + try { + for (byte perm : HDS_PERMS) { + int[] newPerms = new int[mPermMap[perm].length - 1]; + int ind = Arrays.binarySearch(mPermMap[perm], uid); + if (ind < 0) continue; + System.arraycopy(mPermMap[perm], 0, newPerms, 0, ind); + System.arraycopy(mPermMap[perm], ind + 1, newPerms, ind, + mPermMap[perm].length - ind - 1); + mPermMap[perm] = newPerms; + mDest.populatePermissionState(perm, newPerms); + } + } catch (RemoteException e) { + // We will re-init the state when the service comes back up + mDest = null; + // We didn't necessarily finish + mIsUpdateDeferred = true; + } + mHdsUid = -1; + } + } + + private boolean isSpecialHdsPermission(int perm) { + for (var hdsPerm : HDS_PERMS) { + if (perm == hdsPerm) return true; + } + return false; + } + /** Called when full syncing package state to audioserver. */ @GuardedBy("mLock") private void resetNativePackageState() { @@ -223,16 +302,19 @@ public class AudioServerPermissionProvider { @GuardedBy("mLock") /** Return all uids (not app-ids) which currently hold a given permission. Not app-op aware */ - private int[] getUidsHoldingPerm(String perm) { + private int[] getUidsHoldingPerm(int perm) { IntArray acc = new IntArray(); for (int userId : mUserIdSupplier.get()) { for (int appId : mPackageMap.keySet()) { int uid = UserHandle.getUid(userId, appId); - if (mPermissionPredicate.test(uid, perm)) { + if (mPermissionPredicate.test(uid, MONITORED_PERMS[perm])) { acc.add(uid); } } } + if (isSpecialHdsPermission(perm) && mHdsUid != -1) { + acc.add(mHdsUid); + } var unwrapped = acc.toArray(); Arrays.sort(unwrapped); return unwrapped; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 6b258ba41aa3..97326211d18e 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -9656,6 +9656,9 @@ public class AudioService extends IAudioService.Stub case MSG_INIT_SPATIALIZER: onInitSpatializer(); + // the device inventory can only be synchronized after the + // spatializer has been initialized + mDeviceBroker.postSynchronizeAdiDevicesInInventory(null); mAudioEventWakeLock.release(); break; @@ -11415,7 +11418,8 @@ public class AudioService extends IAudioService.Stub deviceState.setAudioDeviceCategory(btAudioDeviceCategory); - mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState); + mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory( + deviceState, true /*syncInventory*/); mDeviceBroker.postPersistAudioDeviceSettings(); mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes(), @@ -11984,8 +11988,9 @@ public class AudioService extends IAudioService.Stub var umi = LocalServices.getService(UserManagerInternal.class); var pmsi = LocalServices.getService(PermissionManagerServiceInternal.class); var provider = new AudioServerPermissionProvider(packageStates, - (Integer uid, String perm) -> (pmsi.checkUidPermission(uid, perm, - Context.DEVICE_ID_DEFAULT) == PackageManager.PERMISSION_GRANTED), + (Integer uid, String perm) -> ActivityManager.checkComponentPermission(perm, uid, + /* owningUid = */ -1, /* exported */true) + == PackageManager.PERMISSION_GRANTED, () -> umi.getUserIds() ); audioPolicy.registerOnStartTask(() -> { @@ -12347,13 +12352,19 @@ public class AudioService extends IAudioService.Stub } @Override - public void addAssistantServiceUid(int uid) { + public void addAssistantServiceUid(int uid, int owningUid) { + if (audioserverPermissions()) { + mPermissionProvider.setIsolatedServiceUid(uid, owningUid); + } sendMsg(mAudioHandler, MSG_ADD_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE, uid, 0, null, 0); } @Override public void removeAssistantServiceUid(int uid) { + if (audioserverPermissions()) { + mPermissionProvider.clearIsolatedServiceUid(uid); + } sendMsg(mAudioHandler, MSG_REMOVE_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE, uid, 0, null, 0); } diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 8008717fea35..0de342807df3 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -142,7 +142,6 @@ public class BtHelper { private static final int SCO_MODE_MAX = 2; private static final int BT_HEARING_AID_GAIN_MIN = -128; - private static final int BT_LE_AUDIO_MIN_VOL = 0; private static final int BT_LE_AUDIO_MAX_VOL = 255; // BtDevice constants currently rolling out under flag protection. Use own @@ -211,8 +210,7 @@ public class BtHelper { //---------------------------------------------------------------------- // Interface for AudioDeviceBroker - // @GuardedBy("mDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") + // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock /*package*/ synchronized void onSystemReady() { mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR; resetBluetoothSco(); @@ -373,8 +371,7 @@ public class BtHelper { return codecAndChanged; } - // @GuardedBy("mDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") + // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock /*package*/ synchronized void onReceiveBtEvent(Intent intent) { final String action = intent.getAction(); @@ -396,78 +393,67 @@ public class BtHelper { } /** - * Exclusively called from AudioDeviceBroker when handling MSG_L_RECEIVED_BT_EVENT + * Exclusively called from AudioDeviceBroker (with mSetModeLock held) + * when handling MSG_L_RECEIVED_BT_EVENT in {@link #onReceiveBtEvent(Intent)} * as part of the serialization of the communication route selection */ - // @GuardedBy("mDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") + @GuardedBy("BtHelper.this") private void onScoAudioStateChanged(int state) { boolean broadcast = false; int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR; - Log.i(TAG, "onScoAudioStateChanged state: " + state + " mScoAudioState: " + mScoAudioState); - if (mDeviceBroker.isScoManagedByAudio()) { - switch (state) { - case BluetoothHeadset.STATE_AUDIO_CONNECTED: - mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged"); - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; + Log.i(TAG, "onScoAudioStateChanged state: " + state + + ", mScoAudioState: " + mScoAudioState); + switch (state) { + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } else if (mDeviceBroker.isBluetoothScoRequested()) { + // broadcast intent if the connection was initated by AudioService broadcast = true; - break; - case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: - mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged"); - scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; - broadcast = true; - break; - default: - break; - } - } else { - switch (state) { - case BluetoothHeadset.STATE_AUDIO_CONNECTED: - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL - && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } else if (mDeviceBroker.isBluetoothScoRequested()) { - // broadcast intent if the connection was initated by AudioService - broadcast = true; - } + } + if (!mDeviceBroker.isScoManagedByAudio()) { mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged"); - break; - case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + } + break; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + if (!mDeviceBroker.isScoManagedByAudio()) { mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged"); - scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; - // There are two cases where we want to immediately reconnect audio: - // 1) If a new start request was received while disconnecting: this was - // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ. - // 2) If audio was connected then disconnected via Bluetooth APIs and - // we still have pending activation requests by apps: this is indicated by - // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested. - if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null - && connectBluetoothScoAudioHelper(mBluetoothHeadset, - mBluetoothHeadsetDevice, mScoAudioMode)) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING; - broadcast = true; - break; - } - } - if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) { + } + scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; + // There are two cases where we want to immediately reconnect audio: + // 1) If a new start request was received while disconnecting: this was + // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ. + // 2) If audio was connected then disconnected via Bluetooth APIs and + // we still have pending activation requests by apps: this is indicated by + // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested. + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null + && connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING; broadcast = true; + break; } - mScoAudioState = SCO_STATE_INACTIVE; - break; - case BluetoothHeadset.STATE_AUDIO_CONNECTING: - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL - && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - break; - default: - break; - } + } + if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) { + broadcast = true; + } + mScoAudioState = SCO_STATE_INACTIVE; + break; + case BluetoothHeadset.STATE_AUDIO_CONNECTING: + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL + && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + break; + default: + break; } if (broadcast) { + Log.i(TAG, "onScoAudioStateChanged broadcasting state: " + scoAudioState); broadcastScoConnectionState(scoAudioState); //FIXME: this is to maintain compatibility with deprecated intent // AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate. @@ -493,16 +479,14 @@ public class BtHelper { || mScoAudioState == SCO_STATE_ACTIVATE_REQ; } - // @GuardedBy("mDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") + // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode, @NonNull String eventSource) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource)); return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode); } - // @GuardedBy("mDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") + // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource)); return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL); @@ -574,8 +558,7 @@ public class BtHelper { mScoConnectionState = state; } - // @GuardedBy("mDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") + // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock /*package*/ synchronized void resetBluetoothSco() { mScoAudioState = SCO_STATE_INACTIVE; broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); @@ -584,8 +567,7 @@ public class BtHelper { mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco"); } - // @GuardedBy("mDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") + // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock /*package*/ synchronized void onBtProfileDisconnected(int profile) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "BT profile " + BluetoothProfile.getProfileName(profile) @@ -649,8 +631,7 @@ public class BtHelper { MyLeAudioCallback mLeAudioCallback = null; - // @GuardedBy("mDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") + // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy " @@ -787,8 +768,7 @@ public class BtHelper { } } - // @GuardedBy("mDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") + // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock private void onHeadsetProfileConnected(@NonNull BluetoothHeadset headset) { // Discard timeout message mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService(); @@ -931,8 +911,7 @@ public class BtHelper { return btDevice == null ? "(null)" : btDevice.getAnonymizedAddress(); } - // @GuardedBy("mDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") + // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock /*package */ synchronized void onSetBtScoActiveDevice(BluetoothDevice btDevice) { Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice) + " -> " + getAnonymizedAddress(btDevice)); @@ -1133,6 +1112,9 @@ public class BtHelper { //----------------------------------------------------- // Utilities + + // suppress warning due to generic Intent passed as param + @SuppressWarnings("AndroidFrameworkRequiresPermission") private void sendStickyBroadcastToAll(Intent intent) { intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final long ident = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 5c74304cabd5..ded93e60cd6f 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -643,9 +643,9 @@ public class SoundDoseHelper { if (index > safeIndex) { streamState.setIndex(safeIndex, deviceType, caller, true /*hasModifyAudioSettings*/); - mAudioHandler.sendMessageAtTime( + mAudioHandler.sendMessage( mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, deviceType, - /*arg2=*/0, streamState), /*delay=*/0); + /*arg2=*/0, streamState)); } } } @@ -686,8 +686,11 @@ public class SoundDoseHelper { /*package*/ void disableSafeMediaVolume(String callingPackage) { synchronized (mSafeMediaVolumeStateLock) { final long identity = Binder.clearCallingIdentity(); - setSafeMediaVolumeEnabled(false, callingPackage); - Binder.restoreCallingIdentity(identity); + try { + setSafeMediaVolumeEnabled(false, callingPackage); + } finally { + Binder.restoreCallingIdentity(identity); + } if (mPendingVolumeCommand != null) { mAudioService.onSetStreamVolume(mPendingVolumeCommand.mStreamType, @@ -701,6 +704,7 @@ public class SoundDoseHelper { } } + @SuppressWarnings("AndroidFrameworkRequiresPermission") /*package*/ void scheduleMusicActiveCheck() { synchronized (mSafeMediaVolumeStateLock) { cancelMusicActiveCheck(); @@ -1035,10 +1039,9 @@ public class SoundDoseHelper { mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED; } - mAudioHandler.sendMessageAtTime( + mAudioHandler.sendMessage( mAudioHandler.obtainMessage(MSG_PERSIST_SAFE_VOLUME_STATE, - persistedState, /*arg2=*/0, - /*obj=*/null), /*delay=*/0); + persistedState, /*arg2=*/0, /*obj=*/null)); } private void updateCsdEnabled(String caller) { @@ -1199,8 +1202,8 @@ public class SoundDoseHelper { sanitizeDoseRecords_l(); - mAudioHandler.sendMessageAtTime(mAudioHandler.obtainMessage(MSG_PERSIST_CSD_VALUES, - /* arg1= */0, /* arg2= */0, /* obj= */null), /* delay= */0); + mAudioHandler.sendMessage(mAudioHandler.obtainMessage(MSG_PERSIST_CSD_VALUES, + /* arg1= */0, /* arg2= */0, /* obj= */null)); mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration)); } @@ -1316,6 +1319,7 @@ public class SoundDoseHelper { } /** Called when handling MSG_LOWER_VOLUME_TO_RS1 */ + @SuppressWarnings("AndroidFrameworkRequiresPermission") private void onLowerVolumeToRs1() { final ArrayList<AudioDeviceAttributes> devices = mAudioService.getDevicesForAttributesInt( new AudioAttributes.Builder().setUsage( @@ -1360,9 +1364,9 @@ public class SoundDoseHelper { @Override public String toString() { - return new StringBuilder().append("{streamType=").append(mStreamType).append(",index=") - .append(mIndex).append(",flags=").append(mFlags).append(",device=") - .append(mDevice).append('}').toString(); + return "{streamType=" + mStreamType + + ",index=" + mIndex + ",flags=" + mFlags + + ",device=" + mDevice + "}"; } } } diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index cae169550d9a..9265ff2d9b2d 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -568,7 +568,8 @@ public class SpatializerHelper { updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), ada.getAddress()); initSAState(updatedDevice); - mDeviceBroker.addOrUpdateDeviceSAStateInInventory(updatedDevice); + mDeviceBroker.addOrUpdateDeviceSAStateInInventory( + updatedDevice, true /*syncInventory*/); } if (updatedDevice != null) { onRoutingUpdated(); @@ -723,7 +724,7 @@ public class SpatializerHelper { new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), ada.getAddress()); initSAState(deviceState); - mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState); + mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState, true /*syncInventory*/); mDeviceBroker.postPersistAudioDeviceSettings(); logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later. } diff --git a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING new file mode 100644 index 000000000000..4a66bac2e4ec --- /dev/null +++ b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "postsubmit": [ + { + "name": "FrameworksMockingServicesTests", + "options": [ + { + "include-filter": "com.android.server.RescuePartyTest" + } + ] + } + ] +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index e8394d43f266..619aecf4156f 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -19,6 +19,9 @@ package com.android.server.devicestate; import static android.Manifest.permission.CONTROL_DEVICE_STATE; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY; +import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY; +import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST; import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS; import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP; import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; @@ -47,6 +50,8 @@ import android.hardware.devicestate.DeviceStateManager; import android.hardware.devicestate.DeviceStateManagerInternal; import android.hardware.devicestate.IDeviceStateManager; import android.hardware.devicestate.IDeviceStateManagerCallback; +import android.hardware.devicestate.feature.flags.FeatureFlags; +import android.hardware.devicestate.feature.flags.FeatureFlagsImpl; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -175,7 +180,7 @@ public final class DeviceStateManagerService extends SystemService { private Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>(); - private Set<Integer> mFoldedDeviceStates; + private Set<Integer> mFoldedDeviceStates = new HashSet<>(); @Nullable private DeviceState mRearDisplayState; @@ -185,6 +190,9 @@ public final class DeviceStateManagerService extends SystemService { @Nullable private OverrideRequest mRearDisplayPendingOverrideRequest; + @NonNull + private final FeatureFlags mFlags; + @VisibleForTesting interface SystemPropertySetter { void setDebugTracingDeviceStateProperty(String value); @@ -245,6 +253,7 @@ public final class DeviceStateManagerService extends SystemService { @NonNull SystemPropertySetter systemPropertySetter) { super(context); mSystemPropertySetter = systemPropertySetter; + mFlags = new FeatureFlagsImpl(); // We use the DisplayThread because this service indirectly drives // display (on/off) and window (position) events through its callbacks. DisplayThread displayThread = DisplayThread.get(); @@ -270,9 +279,12 @@ public final class DeviceStateManagerService extends SystemService { publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService); publishLocalService(DeviceStateManagerInternal.class, new LocalService()); - synchronized (mLock) { - readStatesAvailableForRequestFromApps(); - mFoldedDeviceStates = readFoldedStates(); + if (!mFlags.deviceStatePropertyMigration()) { + synchronized (mLock) { + readStatesAvailableForRequestFromApps(); + mFoldedDeviceStates = readFoldedStates(); + setRearDisplayStateLocked(); + } } mActivityTaskManagerInternal.registerScreenObserver(mOverrideRequestScreenObserver); @@ -461,8 +473,6 @@ public final class DeviceStateManagerService extends SystemService { mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers, reason); updatePendingStateLocked(); - setRearDisplayStateLocked(); - notifyDeviceStateInfoChangedAsync(); mHandler.post(this::notifyPolicyIfNeeded); @@ -838,12 +848,22 @@ public final class DeviceStateManagerService extends SystemService { OverrideRequest request = new OverrideRequest(token, callingPid, callingUid, deviceState.get(), flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE); - // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay - if (!hasControlDeviceStatePermission && mRearDisplayState != null - && state == mRearDisplayState.getIdentifier()) { - showRearDisplayEducationalOverlayLocked(request); + if (mFlags.deviceStatePropertyMigration()) { + // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay + if (!hasControlDeviceStatePermission && deviceState.get().hasProperty( + PROPERTY_FEATURE_REAR_DISPLAY)) { + showRearDisplayEducationalOverlayLocked(request); + } else { + mOverrideRequestController.addRequest(request); + } } else { - mOverrideRequestController.addRequest(request); + // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay + if (!hasControlDeviceStatePermission && mRearDisplayState != null + && state == mRearDisplayState.getIdentifier()) { + showRearDisplayEducationalOverlayLocked(request); + } else { + mOverrideRequestController.addRequest(request); + } } } } @@ -1034,7 +1054,13 @@ public final class DeviceStateManagerService extends SystemService { private boolean isStateAvailableForAppRequests(int state) { synchronized (mLock) { - return mDeviceStatesAvailableForAppRequests.contains(state); + if (mFlags.deviceStatePropertyMigration()) { + Optional<DeviceState> deviceState = getStateLocked(state); + return deviceState.isPresent() && deviceState.get().hasProperty( + PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST); + } else { + return mDeviceStatesAvailableForAppRequests.contains(state); + } } } @@ -1096,9 +1122,20 @@ public final class DeviceStateManagerService extends SystemService { */ @GuardedBy("mLock") private boolean isDeviceOpeningLocked(int newBaseState) { - return mBaseState.filter( - deviceState -> mFoldedDeviceStates.contains(deviceState.getIdentifier()) - && !mFoldedDeviceStates.contains(newBaseState)).isPresent(); + if (mFlags.deviceStatePropertyMigration()) { + final DeviceState currentBaseState = mBaseState.orElse(INVALID_DEVICE_STATE); + final DeviceState newDeviceBaseState = getStateLocked(newBaseState).orElse( + INVALID_DEVICE_STATE); + + return currentBaseState.hasProperty( + PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY) + && !newDeviceBaseState.hasProperty( + PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY); + } else { + return mBaseState.filter( + deviceState -> mFoldedDeviceStates.contains(deviceState.getIdentifier()) + && !mFoldedDeviceStates.contains(newBaseState)).isPresent(); + } } private final class DeviceStateProviderListener implements DeviceStateProvider.Listener { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index e686779e4ccd..43c1f24f2b73 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -598,10 +598,11 @@ public final class DisplayManagerService extends SystemService { FoldSettingProvider foldSettingProvider = new FoldSettingProvider(context, new SettingsWrapper(), new FoldLockSettingAvailabilityProvider(context.getResources())); + Looper displayThreadLooper = DisplayThread.get().getLooper(); mInjector = injector; mContext = context; mFlags = injector.getFlags(); - mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper()); + mHandler = new DisplayManagerHandler(displayThreadLooper); mUiHandler = UiThread.getHandler(); mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore); mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, @@ -609,7 +610,7 @@ public final class DisplayManagerService extends SystemService { mDisplayDeviceRepo, new LogicalDisplayListener(), mSyncRoot, mHandler, mFlags); mDisplayModeDirector = new DisplayModeDirector( context, mHandler, mFlags, mDisplayDeviceConfigProvider); - mBrightnessSynchronizer = new BrightnessSynchronizer(mContext, + mBrightnessSynchronizer = new BrightnessSynchronizer(mContext, displayThreadLooper, mFlags.isBrightnessIntRangeUserPerceptionEnabled()); Resources resources = mContext.getResources(); mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger( diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index de9715ac812d..7cd9144be77b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -2098,7 +2098,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private void onDisplayOffloadUnblockScreenOn(DisplayOffloadSession displayOffloadSession) { Message msg = mHandler.obtainMessage(MSG_OFFLOADING_SCREEN_ON_UNBLOCKED, displayOffloadSession); - mHandler.sendMessage(msg); + mHandler.sendMessageAtTime(msg, mClock.uptimeMillis()); } private void unblockScreenOnByDisplayOffload() { @@ -2116,7 +2116,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mDisplayOffloadSession == null) { return; } - if (mPendingScreenOnUnblockerByDisplayOffload != null) { + if (mPendingScreenOnUnblockerByDisplayOffload == null) { // Already unblocked. return; } @@ -2134,7 +2134,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // If the screen is turning on, give displayoffload a chance to do something before the // screen actually turns on. - // TODO(b/316941732): add tests for this displayoffload screen-on blocker. if (isOn && changed && !mScreenTurningOnWasBlockedByDisplayOffload) { blockScreenOnByDisplayOffload(mDisplayOffloadSession); } else if (!isOn && mScreenTurningOnWasBlockedByDisplayOffload) { diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 18a9986d34ba..886857c1b880 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -1079,6 +1079,21 @@ public final class DreamManagerService extends SystemService { } @Override // Binder call + public void finishSelfOneway(IBinder token, boolean immediate) { + // Requires no permission, called by Dream from an arbitrary process. + if (token == null) { + throw new IllegalArgumentException("token must not be null"); + } + + final long ident = Binder.clearCallingIdentity(); + try { + finishSelfInternal(token, immediate); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call public void startDozing( IBinder token, int screenState, @Display.StateReason int reason, int screenBrightness) { @@ -1096,6 +1111,23 @@ public final class DreamManagerService extends SystemService { } @Override // Binder call + public void startDozingOneway( + IBinder token, int screenState, @Display.StateReason int reason, + int screenBrightness) { + // Requires no permission, called by Dream from an arbitrary process. + if (token == null) { + throw new IllegalArgumentException("token must not be null"); + } + + final long ident = Binder.clearCallingIdentity(); + try { + startDozingInternal(token, screenState, reason, screenBrightness); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call public void stopDozing(IBinder token) { // Requires no permission, called by Dream from an arbitrary process. if (token == null) { diff --git a/services/core/java/com/android/server/health/HealthServiceWrapper.java b/services/core/java/com/android/server/health/HealthServiceWrapper.java index 25d1a885bc18..9c14b5b079b1 100644 --- a/services/core/java/com/android/server/health/HealthServiceWrapper.java +++ b/services/core/java/com/android/server/health/HealthServiceWrapper.java @@ -71,6 +71,21 @@ public abstract class HealthServiceWrapper { public abstract android.hardware.health.HealthInfo getHealthInfo() throws RemoteException; /** + * Calls into getBatteryHealthData() in the health HAL. + * This function does not have a corresponding HIDL implementation, so + * returns null by default, unless there is an AIDL class that overrides + * this one. + * + * @return battery health data. {@code null} if no health HAL service. + * {@code null} if any service-specific error when calling {@code + * getBatteryHealthData}, e.g. it is unsupported. + * @throws RemoteException for any transaction-level errors + */ + public android.hardware.health.BatteryHealthData getBatteryHealthData() throws RemoteException { + return null; + } + + /** * Create a new HealthServiceWrapper instance. * * @param healthInfoCallback the callback to call when health info changes diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java index fd3a92e97c26..2a3fbc3f2466 100644 --- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java +++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java @@ -212,6 +212,17 @@ class HealthServiceWrapperAidl extends HealthServiceWrapper { } } + @Override + public BatteryHealthData getBatteryHealthData() throws RemoteException { + IHealth service = mLastService.get(); + if (service == null) return null; + try { + return service.getBatteryHealthData(); + } catch (UnsupportedOperationException | ServiceSpecificException ex) { + return null; + } + } + public void setChargingPolicy(int policy) throws RemoteException { IHealth service = mLastService.get(); if (service == null) return; diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java index 38a0d37c5679..62c21bda1fd1 100644 --- a/services/core/java/com/android/server/input/BatteryController.java +++ b/services/core/java/com/android/server/input/BatteryController.java @@ -83,6 +83,7 @@ final class BatteryController { private final Handler mHandler; private final UEventManager mUEventManager; private final BluetoothBatteryManager mBluetoothBatteryManager; + private final Runnable mHandlePollEventCallback = this::handlePollEvent; // Maps a pid to the registered listener record for that process. There can only be one battery // listener per process. @@ -206,7 +207,7 @@ final class BatteryController { if (!mIsInteractive || !anyOf(mDeviceMonitors, DeviceMonitor::requiresPolling)) { // Stop polling. mIsPolling = false; - mHandler.removeCallbacks(this::handlePollEvent); + mHandler.removeCallbacks(mHandlePollEventCallback); return; } @@ -215,7 +216,7 @@ final class BatteryController { } // Start polling. mIsPolling = true; - mHandler.postDelayed(this::handlePollEvent, delayStart ? POLLING_PERIOD_MILLIS : 0); + mHandler.postDelayed(mHandlePollEventCallback, delayStart ? POLLING_PERIOD_MILLIS : 0); } private <R> R processInputDevice(int deviceId, R defaultValue, Function<InputDevice, R> func) { @@ -366,7 +367,7 @@ final class BatteryController { } final long eventTime = SystemClock.uptimeMillis(); mDeviceMonitors.forEach((deviceId, monitor) -> monitor.onPoll(eventTime)); - mHandler.postDelayed(this::handlePollEvent, POLLING_PERIOD_MILLIS); + mHandler.postDelayed(mHandlePollEventCallback, POLLING_PERIOD_MILLIS); } } diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java index 0749edce97a1..aeace7ac9aee 100644 --- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java +++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java @@ -103,8 +103,8 @@ final class AutofillSuggestionsController { // Note that current user ID is guaranteed to be userId. final var imeId = mBindingController.getSelectedMethodId(); - final InputMethodInfo imi = InputMethodSettingsRepository.get(mBindingController.mUserId) - .getMethodMap().get(imeId); + final InputMethodInfo imi = InputMethodSettingsRepository.get( + mBindingController.getUserId()).getMethodMap().get(imeId); if (imi == null || !isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) { callback.onInlineSuggestionsUnsupported(); return; diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java index b77f47d640e9..356bc40cb985 100644 --- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java @@ -53,10 +53,10 @@ import com.android.server.wm.WindowManagerInternal; import java.util.Objects; /** - * The default implementation of {@link ImeVisibilityApplier} used in - * {@link InputMethodManagerService}. + * A stateless helper class for IME visibility operations like show/hide and update Z-ordering + * relative to the IME targeted window. */ -final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { +final class DefaultImeVisibilityApplier { private static final String TAG = "DefaultImeVisibilityApplier"; @@ -75,12 +75,22 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { mImeTargetVisibilityPolicy = LocalServices.getService(ImeTargetVisibilityPolicy.class); } + /** + * Performs showing IME on top of the given window. + * + * @param showInputToken a token that represents the requester to show IME + * @param statsToken the token tracking the current IME request + * @param resultReceiver if non-null, this will be called back to the caller when + * it has processed request to tell what it has done + * @param reason yhe reason for requesting to show IME + * @param userId the target user when performing show IME + */ @GuardedBy("ImfLock.class") - @Override - public void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, + void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, @UserIdInt int userId) { final var bindingController = mService.getInputMethodBindingController(userId); + final var userData = mService.getUserData(userId); final IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod != null) { if (DEBUG) { @@ -93,10 +103,10 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_SHOW_IME, statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE, - Objects.toString(mService.mImeBindingState.mFocusedWindow), + Objects.toString(userData.mImeBindingState.mFocusedWindow), InputMethodDebug.softInputDisplayReasonToString(reason), InputMethodDebug.softInputModeToString( - mService.mImeBindingState.mFocusedWindowSoftInputMode)); + userData.mImeBindingState.mFocusedWindowSoftInputMode)); } mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason, statsToken, userId); @@ -104,13 +114,23 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { } } + /** + * Performs hiding IME to the given window + * + * @param hideInputToken a token that represents the requester to hide IME + * @param statsToken the token tracking the current IME request + * @param resultReceiver if non-null, this will be called back to the caller when + * it has processed request to tell what it has done + * @param reason the reason for requesting to hide IME + * @param userId the target user when performing hide IME + */ @GuardedBy("ImfLock.class") - @Override - public void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, + void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, @UserIdInt int userId) { final var bindingController = mService.getInputMethodBindingController(userId); final IInputMethodInvoker curMethod = bindingController.getCurMethod(); + final var userData = mService.getUserData(userId); if (curMethod != null) { // The IME will report its visible state again after the following message finally // delivered to the IME process as an IPC. Hence the inconsistency between @@ -126,10 +146,10 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { if (DEBUG_IME_VISIBILITY) { EventLog.writeEvent(IMF_HIDE_IME, statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE, - Objects.toString(mService.mImeBindingState.mFocusedWindow), + Objects.toString(userData.mImeBindingState.mFocusedWindow), InputMethodDebug.softInputDisplayReasonToString(reason), InputMethodDebug.softInputModeToString( - mService.mImeBindingState.mFocusedWindowSoftInputMode)); + userData.mImeBindingState.mFocusedWindowSoftInputMode)); } mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason, statsToken, userId); @@ -137,14 +157,16 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { } } - @GuardedBy("ImfLock.class") - @Override - public void applyImeVisibility(IBinder windowToken, @NonNull ImeTracker.Token statsToken, - @ImeVisibilityStateComputer.VisibilityState int state, @UserIdInt int userId) { - applyImeVisibility(windowToken, statsToken, state, - SoftInputShowHideReason.NOT_SET /* ignore reason */, userId); - } - + /** + * Applies the IME visibility from {@link android.inputmethodservice.InputMethodService} with + * according to the given visibility state. + * + * @param windowToken the token of a window for applying the IME visibility + * @param statsToken the token tracking the current IME request + * @param state the new IME visibility state for the applier to handle + * @param reason one of {@link SoftInputShowHideReason} + * @param userId the target user when applying the IME visibility state + */ @GuardedBy("ImfLock.class") void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken, @ImeVisibilityStateComputer.VisibilityState int state, @@ -179,29 +201,30 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { break; case STATE_HIDE_IME_EXPLICIT: if (Flags.refactorInsetsController()) { - setImeVisibilityOnFocusedWindowClient(false); + setImeVisibilityOnFocusedWindowClient(false, userId); } else { mService.hideCurrentInputLocked(windowToken, statsToken, - 0 /* flags */, null /* resultReceiver */, reason); + 0 /* flags */, null /* resultReceiver */, reason, userId); } break; case STATE_HIDE_IME_NOT_ALWAYS: if (Flags.refactorInsetsController()) { - setImeVisibilityOnFocusedWindowClient(false); + setImeVisibilityOnFocusedWindowClient(false, userId); } else { mService.hideCurrentInputLocked(windowToken, statsToken, - InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, reason); + InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, reason, + userId); } break; case STATE_SHOW_IME_IMPLICIT: if (Flags.refactorInsetsController()) { // This can be triggered by IMMS#startInputOrWindowGainedFocus. We need to // set the requestedVisibleTypes in InsetsController first, before applying it. - setImeVisibilityOnFocusedWindowClient(true); + setImeVisibilityOnFocusedWindowClient(true, userId); } else { mService.showCurrentInputLocked(windowToken, statsToken, InputMethodManager.SHOW_IMPLICIT, MotionEvent.TOOL_TYPE_UNKNOWN, - null /* resultReceiver */, reason); + null /* resultReceiver */, reason, userId); } break; case STATE_SHOW_IME_SNAPSHOT: @@ -215,9 +238,16 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { } } + /** + * Shows the IME screenshot and attach it to the given IME target window. + * + * @param imeTarget the token of a window to show the IME screenshot + * @param displayId the unique id to identify the display + * @param userId the target user when when showing the IME screenshot + * @return {@code true} if success, {@code false} otherwise + */ @GuardedBy("ImfLock.class") - @Override - public boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId, + boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId, @UserIdInt int userId) { if (mImeTargetVisibilityPolicy.showImeScreenshot(imeTarget, displayId)) { mService.onShowHideSoftInputRequested(false /* show */, imeTarget, @@ -227,23 +257,32 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { return false; } + /** + * Removes the IME screenshot on the given display. + * + * @param displayId the target display of showing IME screenshot + * @param userId the target user of showing IME screenshot + * @return {@code true} if success, {@code false} otherwise + */ @GuardedBy("ImfLock.class") - @Override - public boolean removeImeScreenshot(int displayId, @UserIdInt int userId) { + boolean removeImeScreenshot(int displayId, @UserIdInt int userId) { + final var userData = mService.getUserData(userId); if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) { mService.onShowHideSoftInputRequested(false /* show */, - mService.mImeBindingState.mFocusedWindow, + userData.mImeBindingState.mFocusedWindow, REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */, userId); return true; } return false; } - private void setImeVisibilityOnFocusedWindowClient(boolean visibility) { - if (mService.mImeBindingState != null - && mService.mImeBindingState.mFocusedWindowClient != null - && mService.mImeBindingState.mFocusedWindowClient.mClient != null) { - mService.mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(visibility); + @GuardedBy("ImfLock.class") + private void setImeVisibilityOnFocusedWindowClient(boolean visibility, @UserIdInt int userId) { + final var userData = mService.getUserData(userId); + if (userData.mImeBindingState != null + && userData.mImeBindingState.mFocusedWindowClient != null + && userData.mImeBindingState.mFocusedWindowClient.mClient != null) { + userData.mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(visibility); } else { // TODO(b/329229469): ImeTracker? } diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java index 56fa8c9364f5..14551a1a0033 100644 --- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java +++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java @@ -23,7 +23,6 @@ import android.annotation.Nullable; import android.os.Binder; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.util.Log; import android.view.inputmethod.ImeTracker; @@ -70,8 +69,8 @@ public final class ImeTrackerService extends IImeTracker.Stub { private final Object mLock = new Object(); - ImeTrackerService(@NonNull Looper looper) { - mHandler = new Handler(looper, null /* callback */, true /* async */); + ImeTrackerService(@NonNull Handler handler) { + mHandler = handler; } @NonNull diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java deleted file mode 100644 index c1069f2a23d7..000000000000 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityApplier.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.inputmethod; - -import android.annotation.NonNull; -import android.annotation.UserIdInt; -import android.os.IBinder; -import android.os.ResultReceiver; -import android.view.inputmethod.ImeTracker; -import android.view.inputmethod.InputMethod; - -import com.android.internal.inputmethod.SoftInputShowHideReason; - -/** - * Interface for IME visibility operations like show/hide and update Z-ordering relative to the IME - * targeted window. - */ -interface ImeVisibilityApplier { - /** - * Performs showing IME on top of the given window. - * - * @param showInputToken a token that represents the requester to show IME - * @param statsToken the token tracking the current IME request - * @param resultReceiver if non-null, this will be called back to the caller when - * it has processed request to tell what it has done - * @param reason yhe reason for requesting to show IME - * @param userId the target user when performing show IME - */ - default void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, - @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason, @UserIdInt int userId) { - } - - /** - * Performs hiding IME to the given window - * - * @param hideInputToken a token that represents the requester to hide IME - * @param statsToken the token tracking the current IME request - * @param resultReceiver if non-null, this will be called back to the caller when - * it has processed request to tell what it has done - * @param reason the reason for requesting to hide IME - * @param userId the target user when performing hide IME - */ - default void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, - ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, - @UserIdInt int userId) { - } - - /** - * Applies the IME visibility from {@link android.inputmethodservice.InputMethodService} with - * according to the given visibility state. - * - * @param windowToken the token of a window for applying the IME visibility - * @param statsToken the token tracking the current IME request - * @param state the new IME visibility state for the applier to handle - * @param userId the target user when applying the IME visibility state - */ - default void applyImeVisibility(IBinder windowToken, @NonNull ImeTracker.Token statsToken, - @ImeVisibilityStateComputer.VisibilityState int state, @UserIdInt int userId) { - } - - /** - * Updates the IME Z-ordering relative to the given window. - * - * This used to adjust the IME relative layer of the window during - * {@link InputMethodManagerService} is in switching IME clients. - * - * @param windowToken the token of a window to update the Z-ordering relative to the IME - */ - default void updateImeLayeringByTarget(IBinder windowToken) { - // TODO: add a method in WindowManagerInternal to call DC#updateImeInputAndControlTarget - // here to end up updating IME layering after IMMS#attachNewInputLocked called. - } - - /** - * Shows the IME screenshot and attach it to the given IME target window. - * - * @param windowToken the token of a window to show the IME screenshot - * @param displayId the unique id to identify the display - * @param userId the target user when when showing the IME screenshot - * @return {@code true} if success, {@code false} otherwise - */ - default boolean showImeScreenshot(@NonNull IBinder windowToken, int displayId, - @UserIdInt int userId) { - return false; - } - - /** - * Removes the IME screenshot on the given display. - * - * @param displayId the target display of showing IME screenshot - * @param userId the target user of showing IME screenshot - * @return {@code true} if success, {@code false} otherwise - */ - default boolean removeImeScreenshot(int displayId, @UserIdInt int userId) { - return false; - } -} diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java index 9d80844ee9eb..7ebf5950de16 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java @@ -38,6 +38,7 @@ import static com.android.server.inputmethod.InputMethodManagerService.computeIm import android.accessibilityservice.AccessibilityService; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.UserIdInt; import android.content.res.Configuration; import android.os.Binder; import android.os.IBinder; @@ -52,6 +53,7 @@ import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodManager; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.server.LocalServices; @@ -553,15 +555,17 @@ public final class ImeVisibilityStateComputer { return null; } - IBinder getWindowTokenFrom(IBinder requestImeToken) { + @GuardedBy("ImfLock.class") + IBinder getWindowTokenFrom(IBinder requestImeToken, @UserIdInt int userId) { for (IBinder windowToken : mRequestWindowStateMap.keySet()) { final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); if (state.getRequestImeToken() == requestImeToken) { return windowToken; } } + final var userData = mService.getUserData(userId); // Fallback to the focused window for some edge cases (e.g. relaunching the activity) - return mService.mImeBindingState.mFocusedWindow; + return userData.mImeBindingState.mFocusedWindow; } IBinder getWindowTokenFrom(ImeTargetWindowState windowState) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 60d647dc710f..e1aa3a2ee177 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -70,7 +70,7 @@ final class InputMethodBindingController { /** Time in milliseconds that the IME service has to bind before it is reconnected. */ static final long TIME_TO_RECONNECT = 3 * 1000; - @UserIdInt final int mUserId; + @UserIdInt private final int mUserId; @NonNull private final InputMethodManagerService mService; @NonNull private final Context mContext; @NonNull private final AutofillSuggestionsController mAutofillController; @@ -382,9 +382,9 @@ final class InputMethodBindingController { InputMethodManager .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches(); } - mService.initializeImeLocked(mCurMethod, mCurToken); + mService.initializeImeLocked(mCurMethod, mCurToken, mUserId); mService.scheduleNotifyImeUidToAudioService(mCurMethodUid); - mService.reRequestCurrentClientSessionLocked(); + mService.reRequestCurrentClientSessionLocked(mUserId); mAutofillController.performOnCreateInlineSuggestionsRequest(); } @@ -437,7 +437,7 @@ final class InputMethodBindingController { mLastBindTime = SystemClock.uptimeMillis(); clearCurMethodAndSessions(); mService.clearInputShownLocked(); - mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME); + mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME, mUserId); } } } @@ -483,7 +483,7 @@ final class InputMethodBindingController { @GuardedBy("ImfLock.class") private void clearCurMethodAndSessions() { - mService.clearClientSessionsLocked(); + mService.clearClientSessionsLocked(this); mCurMethod = null; mCurMethodUid = Process.INVALID_UID; } @@ -657,4 +657,9 @@ final class InputMethodBindingController { int getDeviceIdToShowIme() { return mDeviceIdToShowIme; } + + @UserIdInt + int getUserId() { + return mUserId; + } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index a089331b4c8f..1e763247cea6 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -90,7 +90,6 @@ import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.LocaleList; -import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; @@ -468,12 +467,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private final ClientController mClientController; /** - * Holds the current IME binding state info. - */ - @MultiUserUnawareField - ImeBindingState mImeBindingState; - - /** * Set once the system is ready to run third party code. */ @SharedByAllUsersField @@ -492,25 +485,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } - /** - * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method. - * This is to be synchronized with the secure settings keyed with - * {@link Settings.Secure#DEFAULT_INPUT_METHOD}. - * - * <p>This can be transiently {@code null} when the system is re-initializing input method - * settings, e.g., the system locale is just changed.</p> - * - * <p>Note that {@link InputMethodBindingController#getCurId()} is used to track which IME - * is being connected to {@link InputMethodManagerService}.</p> - * - * @see InputMethodBindingController#getCurId() - */ - @GuardedBy("ImfLock.class") - @Nullable - String getSelectedMethodIdLocked() { - return getInputMethodBindingController(mCurrentUserId).getSelectedMethodId(); - } - @GuardedBy("ImfLock.class") @Nullable InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) { @@ -518,13 +492,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } /** - * The client that is currently bound to an input method. - */ - @MultiUserUnawareField - @Nullable - private ClientState mCurClient; - - /** * The last window token that we confirmed that IME started talking to. This is always updated * upon reports from the input method. If the window state is already changed before the report * is handled, this field just keeps the last value. @@ -533,33 +500,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. IBinder mLastImeTargetWindow; /** - * The {@link IRemoteInputConnection} last provided by the current client. - */ - @MultiUserUnawareField - IRemoteInputConnection mCurInputConnection; - - /** - * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to - * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME. - */ - @MultiUserUnawareField - ImeOnBackInvokedDispatcher mCurImeDispatcher; - - /** - * The {@link IRemoteAccessibilityInputConnection} last provided by the current client. - */ - @MultiUserUnawareField - @Nullable - IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection; - - /** - * The {@link EditorInfo} last provided by the current client. - */ - @MultiUserUnawareField - @Nullable - EditorInfo mCurEditorInfo; - - /** * Map of window perceptible states indexed by their associated window tokens. * * The value {@code true} indicates that IME has not been mostly hidden via @@ -570,30 +510,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>(); /** - * The token tracking the current IME show request that is waiting for a connection to an IME, - * otherwise {@code null}. - */ - @Nullable - @MultiUserUnawareField - private ImeTracker.Token mCurStatsToken; - - /** - * {@code true} if the current input method is in fullscreen mode. - */ - @MultiUserUnawareField - boolean mInFullscreenMode; - - /** - * The token we have made for the currently active input method, to - * identify it in the future. - */ - @GuardedBy("ImfLock.class") - @Nullable - IBinder getCurTokenLocked() { - return getInputMethodBindingController(mCurrentUserId).getCurToken(); - } - - /** * The displayId of current active input method. */ @GuardedBy("ImfLock.class") @@ -618,27 +534,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } /** - * Have we called mCurMethod.bindInput()? - */ - @MultiUserUnawareField - boolean mBoundToMethod; - - /** - * Have we called bindInput() for accessibility services? - */ - @MultiUserUnawareField - boolean mBoundToAccessibility; - - /** - * Currently enabled session. - */ - @GuardedBy("ImfLock.class") - @MultiUserUnawareField - SessionState mEnabledSession; - @MultiUserUnawareField - SparseArray<AccessibilitySessionState> mEnabledAccessibilitySessions = new SparseArray<>(); - - /** * True if the device is currently interactive with user. The value is true initially. */ @MultiUserUnawareField @@ -763,13 +658,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, mUserId); mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard( accessibilitySoftKeyboardSetting); + final var userData = getUserData(mUserId); if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) { - hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, - SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE); - } else if (isShowRequestedForCurrentWindow()) { - showCurrentInputLocked(mImeBindingState.mFocusedWindow, + hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, + 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, + mUserId); + } else if (isShowRequestedForCurrentWindow(mUserId)) { + showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, InputMethodManager.SHOW_IMPLICIT, - SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE); + SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, mUserId); } } else if (stylusHandwritingEnabledUri.equals(uri)) { InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches(); @@ -777,13 +674,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches(); } else { boolean enabledChanged = false; - String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId) + String newEnabled = InputMethodSettingsRepository.get(mUserId) .getEnabledInputMethodsStr(); if (!mLastEnabled.equals(newEnabled)) { mLastEnabled = newEnabled; enabledChanged = true; } - updateInputMethodsFromSettingsLocked(enabledChanged); + updateInputMethodsFromSettingsLocked(enabledChanged, mUserId); } } } @@ -846,10 +743,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(userId, settings); } - postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */); + // TODO(b/305849394): Dispatch this to non-current users. + final int userId = mCurrentUserId; + postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId); // If the locale is changed, needs to reset the default ime - resetDefaultImeLocked(mContext); - updateFromSettingsLocked(true); + resetDefaultImeLocked(mContext, userId); + updateFromSettingsLocked(true, userId); } } @@ -1004,7 +903,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!isCurrentUser) { return; } - postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); + postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId); boolean changed = false; @@ -1046,7 +945,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } if (changed) { - updateFromSettingsLocked(false); + updateFromSettingsLocked(false, userId); } } } @@ -1188,8 +1087,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. InputMethodSettingsRepository.put(userId, newSettings); if (mCurrentUserId == userId) { // We need to rebuild IMEs. - postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); - updateInputMethodsFromSettingsLocked(true /* enabledChanged */); + postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId); + updateInputMethodsFromSettingsLocked(true /* enabledChanged */, userId); } else if (mExperimentalConcurrentMultiUserModeEnabled) { experimentalInitializeVisibleBackgroundUserLocked(userId); } @@ -1208,8 +1107,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // Hide soft input before user switch task since switch task may block main handler a while // and delayed the hideCurrentInputLocked(). - hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, - SoftInputShowHideReason.HIDE_SWITCH_USER); + final var userData = getUserData(userId); + hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */, + SoftInputShowHideReason.HIDE_SWITCH_USER, userId); final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId, clientToBeReset); mUserSwitchHandlerTask = task; @@ -1258,8 +1158,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mIoHandler = Handler.createAsync(ioThread.getLooper()); } SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler); - mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null - ? serviceThreadForTesting.getLooper() : Looper.getMainLooper()); + mImeTrackerService = new ImeTrackerService(mHandler); // Note: SettingsObserver doesn't register observers in its constructor. mSettingsObserver = new SettingsObserver(mHandler); mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); @@ -1301,7 +1200,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mClientController = new ClientController(mPackageManagerInternal); mClientController.addClientControllerCallback(c -> onClientRemoved(c)); - mImeBindingState = ImeBindingState.newEmptyState(); mPreventImeStartupUnlessTextEditor = mRes.getBoolean( com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor); @@ -1350,10 +1248,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - private void resetDefaultImeLocked(Context context) { + private void resetDefaultImeLocked(Context context, @UserIdInt int userId) { + final var bindingController = getInputMethodBindingController(userId); // Do not reset the default (current) IME when it is a 3rd-party IME - String selectedMethodId = getSelectedMethodIdLocked(); - final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + String selectedMethodId = bindingController.getSelectedMethodId(); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (selectedMethodId != null && !settings.getMethodMap().get(selectedMethodId).isSystem()) { return; @@ -1368,7 +1267,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (DEBUG) { Slog.i(TAG, "Default found, using " + defIm.getId()); } - setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false); + setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false, userId); } @GuardedBy("ImfLock.class") @@ -1413,22 +1312,23 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private void switchUserOnHandlerLocked(@UserIdInt int newUserId, IInputMethodClientInvoker clientToBeReset) { + final int prevUserId = mCurrentUserId; if (DEBUG) { Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId - + " currentUserId=" + mCurrentUserId); + + " prevUserId=" + prevUserId); } // Clean up stuff for mCurrentUserId, which soon becomes the previous user. // TODO(b/338461930): Check if this is still necessary or not. - onUnbindCurrentMethodByReset(); + onUnbindCurrentMethodByReset(prevUserId); // Note that in b/197848765 we want to see if we can keep the binding alive for better // profile switching. - final var bindingController = getInputMethodBindingController(mCurrentUserId); + final var bindingController = getInputMethodBindingController(prevUserId); bindingController.unbindCurrentMethod(); - unbindCurrentClientLocked(UnbindReason.SWITCH_USER); + unbindCurrentClientLocked(UnbindReason.SWITCH_USER, prevUserId); // Hereafter we start initializing things for "newUserId". @@ -1438,6 +1338,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mSettingsObserver.registerContentObserverLocked(newUserId); mCurrentUserId = newUserId; + final var newUserData = getUserData(newUserId); final String defaultImiId = SecureSettingsWrapper.getString( Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId); @@ -1454,13 +1355,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId); final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId); - postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */); + postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */, + newUserId); if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) { // This is the first time of the user switch and // set the current ime to the proper one. - resetDefaultImeLocked(mContext); + resetDefaultImeLocked(mContext, newUserId); } - updateFromSettingsLocked(true); + updateFromSettingsLocked(true, newUserId); if (initialUserSwitch) { InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( @@ -1479,7 +1381,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // The client is already gone. return; } - cs.mClient.scheduleStartInputIfNecessary(mInFullscreenMode); + cs.mClient.scheduleStartInputIfNecessary(newUserData.mInFullscreenMode); } } @@ -1541,8 +1443,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(currentUserId, newSettings); postInputMethodSettingUpdatedLocked( - !imeSelectedOnBoot /* resetDefaultEnabledIme */); - updateFromSettingsLocked(true); + !imeSelectedOnBoot /* resetDefaultEnabledIme */, currentUserId); + updateFromSettingsLocked(true, currentUserId); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, currentUserId), newSettings.getEnabledInputMethodList()); @@ -1579,14 +1481,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * Returns true iff the caller is identified to be the current input method with the token. * * @param token the window token given to the input method when it was started + * @param userId userId of the calling IME process * @return true if and only if non-null valid token is specified */ @GuardedBy("ImfLock.class") - private boolean calledWithValidTokenLocked(@NonNull IBinder token) { + private boolean calledWithValidTokenLocked(@NonNull IBinder token, @UserIdInt int userId) { if (token == null) { throw new InvalidParameterException("token must not be null."); } - if (token != getCurTokenLocked()) { + final var bindingController = getInputMethodBindingController(userId); + if (token != bindingController.getCurToken()) { Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token." + " uid:" + Binder.getCallingUid() + " token:" + token); return false; @@ -1859,20 +1763,31 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + @GuardedBy("ImfLock.class") + private void onClientRemoved(ClientState client) { + clearClientSessionLocked(client); + clearClientSessionForAccessibilityLocked(client); + // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. + @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> clientRemovedForUser = + userData -> onClientRemovedInternalLocked(client, userData); + mUserDataRepository.forAllUserData(clientRemovedForUser); + } + /** * Hide the IME if the removed user is the current user. */ // TODO(b/325515685): Move this method to InputMethodBindingController @GuardedBy("ImfLock.class") - private void onClientRemoved(ClientState client) { - clearClientSessionLocked(client); - clearClientSessionForAccessibilityLocked(client); - if (mCurClient == client) { - hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, - SoftInputShowHideReason.HIDE_REMOVE_CLIENT); - if (mBoundToMethod) { - mBoundToMethod = false; - IInputMethodInvoker curMethod = getCurMethodLocked(); + private void onClientRemovedInternalLocked(ClientState client, + @NonNull UserDataRepository.UserData userData) { + final int userId = userData.mUserId; + if (userData.mCurClient == client) { + hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */, + SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId); + if (userData.mBoundToMethod) { + userData.mBoundToMethod = false; + final var userBindingController = getInputMethodBindingController(userId); + IInputMethodInvoker curMethod = userBindingController.getCurMethod(); if (curMethod != null) { // When we unbind input, we are unbinding the client, so we always // unbind ime and a11y together. @@ -1880,10 +1795,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. AccessibilityManagerInternal.get().unbindInput(); } } - mBoundToAccessibility = false; - mCurClient = null; - if (mImeBindingState.mFocusedWindowClient == client) { - mImeBindingState = ImeBindingState.newEmptyState(); + userData.mBoundToAccessibility = false; + userData.mCurClient = null; + if (userData.mImeBindingState.mFocusedWindowClient == client) { + userData.mImeBindingState = ImeBindingState.newEmptyState(); } } } @@ -1896,49 +1811,51 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) { - if (mCurClient != null) { + void unbindCurrentClientLocked(@UnbindReason int unbindClientReason, @UserIdInt int userId) { + final var userData = getUserData(userId); + if (userData.mCurClient != null) { if (DEBUG) { - Slog.v(TAG, "unbindCurrentInputLocked: client=" + mCurClient.mClient.asBinder()); + Slog.v(TAG, "unbindCurrentInputLocked: client=" + + userData.mCurClient.mClient.asBinder()); } - if (mBoundToMethod) { - mBoundToMethod = false; - IInputMethodInvoker curMethod = getCurMethodLocked(); + final var bindingController = getInputMethodBindingController(userId); + if (userData.mBoundToMethod) { + userData.mBoundToMethod = false; + IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod != null) { curMethod.unbindInput(); } } - mBoundToAccessibility = false; + userData.mBoundToAccessibility = false; // Since we set active false to current client and set mCurClient to null, let's unbind // all accessibility too. That means, when input method get disconnected (including // switching ime), we also unbind accessibility - mCurClient.mClient.setActive(false /* active */, false /* fullscreen */); + userData.mCurClient.mClient.setActive(false /* active */, false /* fullscreen */); - // TODO(b/325515685): make binding controller user independent. Before this change, the - // following dependencies also need to be user independent: mCurClient, mBoundToMethod, - // getCurMethodLocked(), and mMenuController. - final var bindingController = getInputMethodBindingController(mCurrentUserId); - mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(), + userData.mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(), unbindClientReason); - mCurClient.mSessionRequested = false; - mCurClient.mSessionRequestedForAccessibility = false; - mCurClient = null; - ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); - mCurStatsToken = null; + userData.mCurClient.mSessionRequested = false; + userData.mCurClient.mSessionRequestedForAccessibility = false; + userData.mCurClient = null; + ImeTracker.forLogging().onFailed(userData.mCurStatsToken, + ImeTracker.PHASE_SERVER_WAIT_IME); + userData.mCurStatsToken = null; + // TODO: Make mMenuController multi-user aware mMenuController.hideInputMethodMenuLocked(); } } /** * TODO(b/338404383) Remove - * Called when {@link #resetCurrentMethodAndClientLocked(int)} invoked for clean-up states + * Called when {@link #resetCurrentMethodAndClientLocked(int, int)} invoked for clean-up states * before unbinding the current method. */ @GuardedBy("ImfLock.class") - void onUnbindCurrentMethodByReset() { + void onUnbindCurrentMethodByReset(@UserIdInt int userId) { + final var userData = getUserData(userId); final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull( - mImeBindingState.mFocusedWindow); + userData.mImeBindingState.mFocusedWindow); if (winState != null && !winState.isRequestedImeVisible() && !mVisibilityStateComputer.isInputShown()) { // Normally, the focus window will apply the IME visibility state to @@ -1949,9 +1866,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // As a result, we have to notify WM to apply IME visibility before clearing the // binding states in the first place. final var statsToken = createStatsTokenForFocusedClient(false /* show */, - SoftInputShowHideReason.UNBIND_CURRENT_METHOD); - mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken, - STATE_HIDE_IME, mCurrentUserId); + SoftInputShowHideReason.UNBIND_CURRENT_METHOD, userId); + mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow, + statsToken, STATE_HIDE_IME, SoftInputShowHideReason.NOT_SET /* ignore reason */, + userId); } } @@ -1961,13 +1879,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. */ @GuardedBy("ImfLock.class") boolean hasAttachedClient() { - return mCurClient != null; + return getUserData(mCurrentUserId).mCurClient != null; } @VisibleForTesting void setAttachedClientForTesting(@NonNull ClientState cs) { synchronized (ImfLock.class) { - mCurClient = cs; + getUserData(mCurrentUserId).mCurClient = cs; } } @@ -1983,20 +1901,23 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - private boolean isShowRequestedForCurrentWindow() { + private boolean isShowRequestedForCurrentWindow(@UserIdInt int userId) { + final var userData = getUserData(userId); + // TODO(b/349904272): Make mVisibilityStateComputer multi-user aware final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull( - mImeBindingState.mFocusedWindow); + userData.mImeBindingState.mFocusedWindow); return state != null && state.isRequestedImeVisible(); } @GuardedBy("ImfLock.class") @NonNull - InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) { - final int userId = mCurrentUserId; + InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial, + @UserIdInt int userId) { final var bindingController = getInputMethodBindingController(userId); - if (!mBoundToMethod) { - bindingController.getCurMethod().bindInput(mCurClient.mBinding); - mBoundToMethod = true; + final var userData = getUserData(userId); + if (!userData.mBoundToMethod) { + bindingController.getCurMethod().bindInput(userData.mCurClient.mBinding); + userData.mBoundToMethod = true; } final boolean restarting = !initial; @@ -2004,11 +1925,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final StartInputInfo info = new StartInputInfo(mCurrentUserId, bindingController.getCurToken(), bindingController.getCurTokenDisplayId(), bindingController.getCurId(), startInputReason, - restarting, UserHandle.getUserId(mCurClient.mUid), - mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo, - mImeBindingState.mFocusedWindowSoftInputMode, + restarting, UserHandle.getUserId(userData.mCurClient.mUid), + userData.mCurClient.mSelfReportedDisplayId, + userData.mImeBindingState.mFocusedWindow, userData.mCurEditorInfo, + userData.mImeBindingState.mFocusedWindowSoftInputMode, bindingController.getSequenceNumber()); - mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow); + mImeTargetWindowMap.put(startInputToken, userData.mImeBindingState.mFocusedWindow); mStartInputHistory.addEntry(info); // Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user @@ -2016,33 +1938,34 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // same-user scenarios. // That said ignoring cross-user scenario will never affect IMEs that do not have // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case. - if (userId == UserHandle.getUserId(mCurClient.mUid)) { + if (userId == UserHandle.getUserId(userData.mCurClient.mUid)) { mPackageManagerInternal.grantImplicitAccess(userId, null /* intent */, UserHandle.getAppId(bindingController.getCurMethodUid()), - mCurClient.mUid, true /* direct */); + userData.mCurClient.mUid, true /* direct */); } @InputMethodNavButtonFlags final int navButtonFlags = getInputMethodNavButtonFlagsLocked(); - final SessionState session = mCurClient.mCurSession; - setEnabledSessionLocked(session); - session.mMethod.startInput(startInputToken, mCurInputConnection, mCurEditorInfo, restarting, - navButtonFlags, mCurImeDispatcher); + final SessionState session = userData.mCurClient.mCurSession; + setEnabledSessionLocked(session, userData); + session.mMethod.startInput(startInputToken, userData.mCurInputConnection, + userData.mCurEditorInfo, restarting, navButtonFlags, userData.mCurImeDispatcher); if (Flags.refactorInsetsController()) { - if (isShowRequestedForCurrentWindow() && mImeBindingState != null - && mImeBindingState.mFocusedWindow != null) { - showSoftInputInternal(mImeBindingState.mFocusedWindow); + if (isShowRequestedForCurrentWindow(userId) && userData.mImeBindingState != null + && userData.mImeBindingState.mFocusedWindow != null) { + showSoftInputInternal(userData.mImeBindingState.mFocusedWindow); } } else { - if (isShowRequestedForCurrentWindow()) { + if (isShowRequestedForCurrentWindow(userId)) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); // Re-use current statsToken, if it exists. - final var statsToken = mCurStatsToken != null ? mCurStatsToken + final var statsToken = userData.mCurStatsToken != null ? userData.mCurStatsToken : createStatsTokenForFocusedClient(true /* show */, - SoftInputShowHideReason.ATTACH_NEW_INPUT); - mCurStatsToken = null; - showCurrentInputLocked(mImeBindingState.mFocusedWindow, statsToken, + SoftInputShowHideReason.ATTACH_NEW_INPUT, userId); + userData.mCurStatsToken = null; + showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, statsToken, mVisibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN, - null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT); + null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT, + userId); } } @@ -2052,7 +1975,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = - createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions); + createAccessibilityInputMethodSessions( + userData.mCurClient.mAccessibilitySessions); if (bindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) { mHwController.setInkWindowInitializer(new InkWindowInitializer()); } @@ -2064,10 +1988,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private void attachNewAccessibilityLocked(@StartInputReason int startInputReason, - boolean initial) { - if (!mBoundToAccessibility) { + boolean initial, @UserIdInt int userId) { + final var userData = getUserData(userId); + + if (!userData.mBoundToAccessibility) { AccessibilityManagerInternal.get().bindInput(); - mBoundToAccessibility = true; + userData.mBoundToAccessibility = true; } // TODO(b/187453053): grantImplicitAccess to accessibility services access? if so, need to @@ -2076,9 +2002,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // We don't start input when session for a11y is created. We start input when // input method start input, a11y manager service is always on. if (startInputReason != StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY) { - setEnabledSessionForAccessibilityLocked(mCurClient.mAccessibilitySessions); - AccessibilityManagerInternal.get().startInput(mCurRemoteAccessibilityInputConnection, - mCurEditorInfo, !initial /* restarting */); + setEnabledSessionForAccessibilityLocked(userData.mCurClient.mAccessibilitySessions, + userData); + AccessibilityManagerInternal.get().startInput( + userData.mCurRemoteAccessibilityInputConnection, + userData.mCurEditorInfo, !initial /* restarting */); } } @@ -2114,10 +2042,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull InputMethodBindingController bindingController) { + final int userId = bindingController.getUserId(); + final var userData = getUserData(userId); + // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull( - mImeBindingState.mFocusedWindow); + userData.mImeBindingState.mFocusedWindow); if (winState == null) { return InputBindResult.NOT_IME_TARGET_WINDOW; } @@ -2128,19 +2059,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Potentially override the selected input method if the new display belongs to a virtual // device with a custom IME. String selectedMethodId = bindingController.getSelectedMethodId(); - final String deviceMethodId = computeCurrentDeviceMethodIdLocked(bindingController.mUserId, - selectedMethodId); + final String deviceMethodId = computeCurrentDeviceMethodIdLocked( + bindingController.getUserId(), selectedMethodId); if (deviceMethodId == null) { mVisibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true); } else if (!Objects.equals(deviceMethodId, selectedMethodId)) { setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_ID, - bindingController.getDeviceIdToShowIme()); + bindingController.getDeviceIdToShowIme(), userId); selectedMethodId = deviceMethodId; } if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) { - hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, - SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE); + hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */, + SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE, userId); return InputBindResult.NO_IME; } @@ -2149,19 +2080,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return InputBindResult.NO_IME; } - if (mCurClient != cs) { - prepareClientSwitchLocked(cs); + if (userData.mCurClient != cs) { + prepareClientSwitchLocked(cs, userId); } - final boolean connectionWasActive = mCurInputConnection != null; + final boolean connectionWasActive = userData.mCurInputConnection != null; // Bump up the sequence for this client and attach it. bindingController.advanceSequenceNumber(); - mCurClient = cs; - mCurInputConnection = inputConnection; - mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection; - mCurImeDispatcher = imeDispatcher; + userData.mCurClient = cs; + userData.mCurInputConnection = inputConnection; + userData.mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection; + userData.mCurImeDispatcher = imeDispatcher; // Override the locale hints if the app is running on a virtual device. if (mVdmInternal == null) { mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class); @@ -2172,17 +2103,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. editorInfo.hintLocales = hintsFromVirtualDevice; } } - mCurEditorInfo = editorInfo; + userData.mCurEditorInfo = editorInfo; // Notify input manager if the connection state changes. - final boolean connectionIsActive = mCurInputConnection != null; + final boolean connectionIsActive = userData.mCurInputConnection != null; if (connectionIsActive != connectionWasActive) { mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive); } // If configured, we want to avoid starting up the IME if it is not supposed to be showing if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags, - unverifiedTargetSdkVersion)) { + unverifiedTargetSdkVersion, userId)) { if (DEBUG) { Slog.d(TAG, "Avoiding IME startup and unbinding current input method."); } @@ -2197,7 +2128,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final String curId = bindingController.getCurId(); final int displayIdToShowIme = bindingController.getDisplayIdToShowIme(); if (curId != null && curId.equals(bindingController.getSelectedMethodId()) - && displayIdToShowIme == getCurTokenDisplayIdLocked()) { + && displayIdToShowIme == bindingController.getCurTokenDisplayId()) { if (cs.mCurSession != null) { // Fast case: if we are already connected to the input method, // then just return it. @@ -2211,12 +2142,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // we can always attach to accessibility because AccessibilityManagerService is // always on. attachNewAccessibilityLocked(startInputReason, - (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0); + (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0, userId); return attachNewInputLocked(startInputReason, - (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0); + (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0, userId); } - InputBindResult bindResult = tryReuseConnectionLocked(bindingController, cs); + InputBindResult bindResult = tryReuseConnectionLocked(bindingController, cs, userId); if (bindResult != null) { return bindResult; } @@ -2293,18 +2224,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private boolean shouldPreventImeStartupLocked( @NonNull String selectedMethodId, @StartInputFlags int startInputFlags, - int unverifiedTargetSdkVersion) { + int unverifiedTargetSdkVersion, + @UserIdInt int userId) { // Fast-path for the majority of cases if (!mPreventImeStartupUnlessTextEditor) { return false; } - if (isShowRequestedForCurrentWindow()) { + if (isShowRequestedForCurrentWindow(userId)) { return false; } if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) { return false; } - final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId) + final InputMethodInfo imi = InputMethodSettingsRepository.get(userId) .getMethodMap().get(selectedMethodId); if (imi == null) { return false; @@ -2316,10 +2248,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - private void prepareClientSwitchLocked(ClientState cs) { + private void prepareClientSwitchLocked(ClientState cs, @UserIdInt int userId) { // If the client is changing, we need to switch over to the new // one. - unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT); + unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT, userId); // If the screen is on, inform the new client it is active if (mIsInteractive) { cs.mClient.setActive(true /* active */, false /* fullscreen */); @@ -2329,13 +2261,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") @Nullable private InputBindResult tryReuseConnectionLocked( - @NonNull InputMethodBindingController bindingController, @NonNull ClientState cs) { + @NonNull InputMethodBindingController bindingController, @NonNull ClientState cs, + @UserIdInt int userId) { if (bindingController.hasMainConnection()) { - if (getCurMethodLocked() != null) { + if (bindingController.getCurMethod() != null) { if (!Flags.useZeroJankProxy()) { // Return to client, and we will get back with it when // we have had a session made for it. - requestClientSessionLocked(cs); + requestClientSessionLocked(cs, userId); requestClientSessionForAccessibilityLocked(cs); } return new InputBindResult( @@ -2361,7 +2294,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. bindingController.getSequenceNumber(), false); } else { EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, - getSelectedMethodIdLocked(), bindingDuration, 0); + bindingController.getSelectedMethodId(), bindingDuration, 0); } } } @@ -2402,12 +2335,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) { + void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token, + @UserIdInt int userId) { if (DEBUG) { Slog.v(TAG, "Sending attach of token: " + token + " for display: " - + getCurTokenDisplayIdLocked()); + + getInputMethodBindingController(userId).getCurTokenDisplayId()); } - inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token), + inputMethod.initializeInternal(token, + new InputMethodPrivilegedOperationsImpl(this, token, userId), + // TODO(b/345519864): Make getInputMethodNavButtonFlagsLocked() multi-user aware getInputMethodNavButtonFlagsLocked()); } @@ -2438,7 +2374,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread void onSessionCreated(IInputMethodInvoker method, IInputMethodSession session, - InputChannel channel) { + InputChannel channel, @UserIdInt int userId) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated"); try { synchronized (ImfLock.class) { @@ -2448,18 +2384,21 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. channel.dispose(); return; } - IInputMethodInvoker curMethod = getCurMethodLocked(); + final var bindingController = getInputMethodBindingController(userId); + final var userData = getUserData(userId); + IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod != null && method != null && curMethod.asBinder() == method.asBinder()) { - if (mCurClient != null) { - clearClientSessionLocked(mCurClient); - mCurClient.mCurSession = new SessionState( - mCurClient, method, session, channel); + if (userData.mCurClient != null) { + clearClientSessionLocked(userData.mCurClient); + userData.mCurClient.mCurSession = new SessionState( + userData.mCurClient, method, session, channel); InputBindResult res = attachNewInputLocked( - StartInputReason.SESSION_CREATED_BY_IME, true); - attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_IME, true); + StartInputReason.SESSION_CREATED_BY_IME, true, userId); + attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_IME, true, + userId); if (res.method != null) { - mCurClient.mClient.onBindMethod(res); + userData.mCurClient.mClient.onBindMethod(res); } return; } @@ -2482,29 +2421,31 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) { - final var bindingController = getInputMethodBindingController(mCurrentUserId); + void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason, + @UserIdInt int userId) { + final var bindingController = getInputMethodBindingController(userId); bindingController.setSelectedMethodId(null); // Callback before clean-up binding states. // TODO(b/338461930): Check if this is still necessary or not. - onUnbindCurrentMethodByReset(); + onUnbindCurrentMethodByReset(userId); bindingController.unbindCurrentMethod(); - unbindCurrentClientLocked(unbindClientReason); + unbindCurrentClientLocked(unbindClientReason, userId); } @GuardedBy("ImfLock.class") - void reRequestCurrentClientSessionLocked() { - if (mCurClient != null) { - clearClientSessionLocked(mCurClient); - clearClientSessionForAccessibilityLocked(mCurClient); - requestClientSessionLocked(mCurClient); - requestClientSessionForAccessibilityLocked(mCurClient); + void reRequestCurrentClientSessionLocked(@UserIdInt int userId) { + final var userData = getUserData(userId); + if (userData.mCurClient != null) { + clearClientSessionLocked(userData.mCurClient); + clearClientSessionForAccessibilityLocked(userData.mCurClient); + requestClientSessionLocked(userData.mCurClient, userId); + requestClientSessionForAccessibilityLocked(userData.mCurClient); } } @GuardedBy("ImfLock.class") - void requestClientSessionLocked(ClientState cs) { + void requestClientSessionLocked(ClientState cs, @UserIdInt int userId) { if (!cs.mSessionRequested) { if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs); final InputChannel serverChannel; @@ -2517,14 +2458,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. cs.mSessionRequested = true; - final IInputMethodInvoker curMethod = getCurMethodLocked(); + final var bindingController = getInputMethodBindingController(userId); + final IInputMethodInvoker curMethod = bindingController.getCurMethod(); final IInputMethodSessionCallback.Stub callback = new IInputMethodSessionCallback.Stub() { @Override public void sessionCreated(IInputMethodSession session) { final long ident = Binder.clearCallingIdentity(); try { - onSessionCreated(curMethod, session, serverChannel); + onSessionCreated(curMethod, session, serverChannel, userId); } finally { Binder.restoreCallingIdentity(ident); } @@ -2615,33 +2557,42 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - void clearClientSessionsLocked() { - if (getCurMethodLocked() != null) { + void clearClientSessionsLocked(@NonNull InputMethodBindingController bindingController) { + final int userId = bindingController.getUserId(); + final var userData = getUserData(userId); + if (bindingController.getCurMethod() != null) { // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. @SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession = c -> { - clearClientSessionLocked(c); - clearClientSessionForAccessibilityLocked(c); + // TODO(b/305849394): Figure out what we should do for single user IME mode. + final boolean shouldClearClientSession = + !mExperimentalConcurrentMultiUserModeEnabled + || UserHandle.getUserId(c.mUid) == userId; + if (shouldClearClientSession) { + clearClientSessionLocked(c); + clearClientSessionForAccessibilityLocked(c); + } }; mClientController.forAllClients(clearClientSession); - finishSessionLocked(mEnabledSession); - for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) { - finishSessionForAccessibilityLocked(mEnabledAccessibilitySessions.valueAt(i)); + finishSessionLocked(userData.mEnabledSession); + for (int i = 0; i < userData.mEnabledAccessibilitySessions.size(); i++) { + finishSessionForAccessibilityLocked( + userData.mEnabledAccessibilitySessions.valueAt(i)); } - mEnabledSession = null; - mEnabledAccessibilitySessions.clear(); + userData.mEnabledSession = null; + userData.mEnabledAccessibilitySessions.clear(); scheduleNotifyImeUidToAudioService(Process.INVALID_UID); } hideStatusBarIconLocked(); - mInFullscreenMode = false; + getUserData(userId).mInFullscreenMode = false; mWindowManagerInternal.setDismissImeOnBackKeyPressed(false); } @BinderThread private void updateStatusIcon(@NonNull IBinder token, String packageName, - @DrawableRes int iconId) { + @DrawableRes int iconId, @UserIdInt int userId) { synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { return; } final long ident = Binder.clearCallingIdentity(); @@ -2783,24 +2734,26 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread @SuppressWarnings("deprecation") - private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) { + private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition, + @UserIdInt int userId) { final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId(); synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { return; } + final var bindingController = getInputMethodBindingController(userId); // Skip update IME status when current token display is not same as focused display. // Note that we still need to update IME status when focusing external display // that does not support system decoration and fallback to show IME on default // display since it is intentional behavior. - final int tokenDisplayId = getCurTokenDisplayIdLocked(); + final int tokenDisplayId = bindingController.getCurTokenDisplayId(); if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) { return; } mImeWindowVis = vis; mBackDisposition = backDisposition; - updateSystemUiLocked(vis, backDisposition); + updateSystemUiLocked(vis, backDisposition, userId); } final boolean dismissImeOnBackKeyPressed; @@ -2820,9 +2773,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @BinderThread - private void reportStartInput(@NonNull IBinder token, IBinder startInputToken) { + private void reportStartInput(@NonNull IBinder token, IBinder startInputToken, + @UserIdInt int userId) { synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { return; } final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken); @@ -2857,6 +2811,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private void updateSystemUiLocked(int vis, int backDisposition, @UserIdInt int userId) { final var bindingController = getInputMethodBindingController(userId); + final var userData = getUserData(userId); final var curToken = bindingController.getCurToken(); if (curToken == null) { return; @@ -2868,8 +2823,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + " inv: " + (vis & InputMethodService.IME_INVISIBLE) + " displayId: " + curTokenDisplayId); } - final IBinder focusedWindowToken = mImeBindingState != null - ? mImeBindingState.mFocusedWindow : null; + final IBinder focusedWindowToken = userData.mImeBindingState != null + ? userData.mImeBindingState.mFocusedWindow : null; final Boolean windowPerceptible = focusedWindowToken != null ? mFocusedWindowPerceptible.get(focusedWindowToken) : null; @@ -2904,8 +2859,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - void updateFromSettingsLocked(boolean enabledMayChange) { - updateInputMethodsFromSettingsLocked(enabledMayChange); + void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) { + updateInputMethodsFromSettingsLocked(enabledMayChange, userId); mMenuController.updateKeyboardFromSettingsLocked(); } @@ -2915,7 +2870,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * * <p>Never assume what this method is doing is officially supported. For the canonical and * desired behaviors always refer to single-user code paths such as - * {@link #updateInputMethodsFromSettingsLocked(boolean)}.</p> + * {@link #updateInputMethodsFromSettingsLocked(boolean, int)}.</p> * * <p>Here are examples of missing features.</p> * <ul> @@ -2964,8 +2919,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) { - final int userId = mCurrentUserId; + void updateInputMethodsFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) { final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (enabledMayChange) { final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext, @@ -2996,7 +2950,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } - final var bindingController = getInputMethodBindingController(mCurrentUserId); + final var bindingController = getInputMethodBindingController(userId); if (bindingController.getDeviceIdToShowIme() == DEVICE_ID_DEFAULT) { String ime = SecureSettingsWrapper.getString( Settings.Secure.DEFAULT_INPUT_METHOD, null, userId); @@ -3026,14 +2980,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } if (!TextUtils.isEmpty(id)) { try { - setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id)); + setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id), userId); } catch (IllegalArgumentException e) { Slog.w(TAG, "Unknown input method from prefs: " + id, e); - resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED); + resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED, userId); } } else { // There is no longer an input method set, so stop any current one. - resetCurrentMethodAndClientLocked(UnbindReason.NO_IME); + resetCurrentMethodAndClientLocked(UnbindReason.NO_IME, userId); } final var userData = getUserData(userId); @@ -3055,13 +3009,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - void setInputMethodLocked(String id, int subtypeId) { - setInputMethodLocked(id, subtypeId, DEVICE_ID_DEFAULT); + void setInputMethodLocked(String id, int subtypeId, @UserIdInt int userId) { + setInputMethodLocked(id, subtypeId, DEVICE_ID_DEFAULT, userId); } @GuardedBy("ImfLock.class") - void setInputMethodLocked(String id, int subtypeId, int deviceId) { - final int userId = mCurrentUserId; + void setInputMethodLocked(String id, int subtypeId, int deviceId, @UserIdInt int userId) { final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); InputMethodInfo info = settings.getMethodMap().get(id); if (info == null) { @@ -3096,8 +3049,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } if (!Objects.equals(newSubtype, oldSubtype)) { - setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); - IInputMethodInvoker curMethod = getCurMethodLocked(); + setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true, userId); + IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod != null) { updateSystemUiLocked(mImeWindowVis, mBackDisposition); curMethod.changeInputMethodSubtype(newSubtype); @@ -3116,7 +3069,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. settings.putSelectedDefaultDeviceInputMethod(id); return; } - IInputMethodInvoker curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod != null) { curMethod.removeStylusHandwritingWindow(); } @@ -3124,7 +3077,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. try { // Set a subtype to this input method. // subtypeId the name of a subtype which will be set. - setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false); + setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false, userId); // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked() // because mCurMethodId is stored as a history in // setSelectedInputMethodAndSubtypeLocked(). @@ -3136,7 +3089,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. intent.putExtra("input_method_id", id); mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); } - unbindCurrentClientLocked(UnbindReason.SWITCH_IME); + unbindCurrentClientLocked(UnbindReason.SWITCH_IME, userId); } finally { Binder.restoreCallingIdentity(ident); } @@ -3158,14 +3111,20 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return false; } + // TODO(b/305849394): Create a utility method for the following policy. + final int userId = mExperimentalConcurrentMultiUserModeEnabled + ? UserHandle.getCallingUserId() : mCurrentUserId; final long ident = Binder.clearCallingIdentity(); + final var userData = getUserData(userId); try { if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); if (Flags.refactorInsetsController()) { boolean wasVisible = isInputShownLocked(); - if (mImeBindingState != null && mImeBindingState.mFocusedWindowClient != null - && mImeBindingState.mFocusedWindowClient.mClient != null) { - mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(true); + if (userData.mImeBindingState != null + && userData.mImeBindingState.mFocusedWindowClient != null + && userData.mImeBindingState.mFocusedWindowClient.mClient != null) { + userData.mImeBindingState.mFocusedWindowClient.mClient + .setImeVisibility(true); if (resultReceiver != null) { resultReceiver.send( wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN @@ -3176,7 +3135,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; } else { return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType, - resultReceiver, reason); + resultReceiver, reason, userId); } } finally { Binder.restoreCallingIdentity(ident); @@ -3190,12 +3149,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. ImeTracing.getInstance().triggerManagerServiceDump( "InputMethodManagerService#showSoftInput", mDumper); synchronized (ImfLock.class) { + // TODO(b/305849394): Infer userId from windowToken + final int userId = mCurrentUserId; final long ident = Binder.clearCallingIdentity(); try { if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); return showCurrentInputLocked(windowToken, null /* statsToken */, 0 /* flags */, 0 /* lastClickTooType */, null /* resultReceiver */, - SoftInputShowHideReason.SHOW_SOFT_INPUT); + SoftInputShowHideReason.SHOW_SOFT_INPUT, userId); } finally { Binder.restoreCallingIdentity(ident); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); @@ -3208,11 +3169,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. ImeTracing.getInstance().triggerManagerServiceDump( "InputMethodManagerService#hideSoftInput", mDumper); synchronized (ImfLock.class) { + // TODO(b/305849394): Infer userId from windowToken + final int userId = mCurrentUserId; final long ident = Binder.clearCallingIdentity(); try { if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); return hideCurrentInputLocked(windowToken, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, SoftInputShowHideReason.HIDE_SOFT_INPUT); + null /* resultReceiver */, SoftInputShowHideReason.HIDE_SOFT_INPUT, + userId); } finally { Binder.restoreCallingIdentity(ident); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); @@ -3342,7 +3306,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; } if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started"); - final IInputMethodInvoker curMethod = getCurMethodLocked(); + final IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod != null) { curMethod.canStartStylusHandwriting(requestId.getAsInt(), connectionlessCallback, cursorAnchorInfo, @@ -3414,8 +3378,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; } synchronized (ImfLock.class) { + final var bindingController = getInputMethodBindingController(userId); if (mHwController.isDelegationUsingConnectionlessFlow()) { - final IInputMethodInvoker curMethod = getCurMethodLocked(); + final IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod == null) { return false; } @@ -3465,7 +3430,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Objects.requireNonNull(windowToken, "windowToken must not be null"); synchronized (ImfLock.class) { Boolean windowPerceptible = mFocusedWindowPerceptible.get(windowToken); - if (mImeBindingState.mFocusedWindow != windowToken + final int userId = mCurrentUserId; + final var userData = getUserData(userId); + if (userData.mImeBindingState.mFocusedWindow != windowToken || (windowPerceptible != null && windowPerceptible == perceptible)) { return; } @@ -3477,17 +3444,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private boolean showCurrentInputLocked(IBinder windowToken, - @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) { - final var statsToken = createStatsTokenForFocusedClient(true /* show */, reason); + @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, + @UserIdInt int userId) { + final var statsToken = createStatsTokenForFocusedClient(true /* show */, reason, userId); return showCurrentInputLocked(windowToken, statsToken, flags, - MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason); + MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason, userId); } @GuardedBy("ImfLock.class") boolean showCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, @MotionEvent.ToolType int lastClickToolType, @Nullable ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) { + @SoftInputShowHideReason int reason, @UserIdInt int userId) { if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) { return false; } @@ -3500,22 +3468,24 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mVisibilityStateComputer.requestImeVisibility(windowToken, true); - final int userId = mCurrentUserId; // Ensure binding the connection when IME is going to show. final var bindingController = getInputMethodBindingController(userId); + final var userData = getUserData(userId); bindingController.setCurrentMethodVisible(); final IInputMethodInvoker curMethod = bindingController.getCurMethod(); - ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); + ImeTracker.forLogging().onCancelled(userData.mCurStatsToken, + ImeTracker.PHASE_SERVER_WAIT_IME); final boolean readyToDispatchToIme; if (Flags.deferShowSoftInputUntilSessionCreation()) { readyToDispatchToIme = - curMethod != null && mCurClient != null && mCurClient.mCurSession != null; + curMethod != null && userData.mCurClient != null + && userData.mCurClient.mCurSession != null; } else { readyToDispatchToIme = curMethod != null; } if (readyToDispatchToIme) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME); - mCurStatsToken = null; + userData.mCurStatsToken = null; if (Flags.useHandwritingListenerForTooltype()) { maybeReportToolType(); @@ -3529,7 +3499,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return true; } else { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME); - mCurStatsToken = statsToken; + userData.mCurStatsToken = statsToken; } return false; } @@ -3575,16 +3545,22 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } return false; } + // TODO(b/305849394): Create a utility method for the following policy. + final int userId = mExperimentalConcurrentMultiUserModeEnabled + ? UserHandle.getCallingUserId() : mCurrentUserId; final long ident = Binder.clearCallingIdentity(); + final var userData = getUserData(userId); try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput"); if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); if (Flags.refactorInsetsController()) { - if (mImeBindingState != null && mImeBindingState.mFocusedWindowClient != null - && mImeBindingState.mFocusedWindowClient.mClient != null) { + if (userData.mImeBindingState != null + && userData.mImeBindingState.mFocusedWindowClient != null + && userData.mImeBindingState.mFocusedWindowClient.mClient != null) { boolean wasVisible = isInputShownLocked(); // TODO add windowToken to interface - mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(false); + userData.mImeBindingState.mFocusedWindowClient.mClient + .setImeVisibility(false); if (resultReceiver != null) { resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null); @@ -3594,7 +3570,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; } else { return InputMethodManagerService.this.hideCurrentInputLocked(windowToken, - statsToken, flags, resultReceiver, reason); + statsToken, flags, resultReceiver, reason, userId); } } finally { Binder.restoreCallingIdentity(ident); @@ -3607,23 +3583,27 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD) public void hideSoftInputFromServerForTest() { synchronized (ImfLock.class) { - hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, - SoftInputShowHideReason.HIDE_SOFT_INPUT); + // TODO(b/305849394): Get userId from caller. + final int userId = mCurrentUserId; + final var userData = getUserData(userId); + hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */, + SoftInputShowHideReason.HIDE_SOFT_INPUT, userId); } } @GuardedBy("ImfLock.class") private boolean hideCurrentInputLocked(IBinder windowToken, - @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) { - final var statsToken = createStatsTokenForFocusedClient(false /* show */, reason); + @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, + @UserIdInt int userId) { + final var statsToken = createStatsTokenForFocusedClient(false /* show */, reason, userId); return hideCurrentInputLocked(windowToken, statsToken, flags, null /* resultReceiver */, - reason); + reason, userId); } @GuardedBy("ImfLock.class") boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) { + @SoftInputShowHideReason int reason, @UserIdInt int userId) { if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) { return false; } @@ -3636,8 +3616,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only // IMMS#InputShown indicates that the software keyboard is shown. // TODO(b/246309664): Clean up IMMS#mImeWindowVis - final int userId = mCurrentUserId; final var bindingController = getInputMethodBindingController(userId); + final var userData = getUserData(userId); IInputMethodInvoker curMethod = bindingController.getCurMethod(); final boolean shouldHideSoftInput = curMethod != null && (isInputShownLocked() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0); @@ -3657,8 +3637,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. bindingController.setCurrentMethodNotVisible(); mVisibilityStateComputer.clearImeShowFlags(); // Cancel existing statsToken for show IME as we got a hide request. - ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); - mCurStatsToken = null; + ImeTracker.forLogging().onCancelled(userData.mCurStatsToken, + ImeTracker.PHASE_SERVER_WAIT_IME); + userData.mCurStatsToken = null; return shouldHideSoftInput; } @@ -3728,7 +3709,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return new InputBindResult( InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, null /* method */, null /* accessibilitySessions */, null /* channel */, - getSelectedMethodIdLocked(), + bindingController.getSelectedMethodId(), bindingController.getSequenceNumber(), false /* isInputMethodSuppressingSpellChecker */); } @@ -3788,7 +3769,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean shouldClearFlag = mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid); final boolean showForced = mVisibilityStateComputer.mShowForced; - if (mImeBindingState.mFocusedWindow != windowToken + final var userData = getUserData(userId); + if (userData.mImeBindingState.mFocusedWindow != windowToken && showForced && shouldClearFlag) { mVisibilityStateComputer.mShowForced = false; } @@ -3807,8 +3789,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Slog.w(TAG, "If you need to impersonate a foreground user/profile from" + " a background user, use EditorInfo.targetInputMethodUser with" + " INTERACT_ACROSS_USERS_FULL permission."); - hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, - SoftInputShowHideReason.HIDE_INVALID_USER); + hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, + 0 /* flags */, SoftInputShowHideReason.HIDE_INVALID_USER, userId); return InputBindResult.INVALID_USER; } @@ -3868,7 +3850,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + " cs=" + cs); } - final boolean sameWindowFocused = mImeBindingState.mFocusedWindow == windowToken; + final int userId = bindingController.getUserId(); + final var userData = getUserData(userId); + final boolean sameWindowFocused = userData.mImeBindingState.mFocusedWindow == windowToken; final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0; final boolean startInputByWinGainedFocus = (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0; @@ -3900,7 +3884,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. null, null, null, null, -1, false); } - mImeBindingState = new ImeBindingState(bindingController.mUserId, windowToken, + userData.mImeBindingState = new ImeBindingState(bindingController.getUserId(), windowToken, softInputMode, cs, editorInfo); mFocusedWindowPerceptible.put(windowToken, true); @@ -3931,16 +3915,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } break; } - final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason()); - mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken, - imeVisRes.getState(), imeVisRes.getReason()); + final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason(), + userId); + mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow, + statsToken, imeVisRes.getState(), imeVisRes.getReason(), userId); if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) { // If focused display changed, we should unbind current method // to make app window in previous display relayout after Ime // window token removed. // Note that we can trust client's display ID as long as it matches // to the display ID obtained from the window. - if (cs.mSelfReportedDisplayId != getCurTokenDisplayIdLocked()) { + if (cs.mSelfReportedDisplayId != bindingController.getCurTokenDisplayId()) { bindingController.unbindCurrentMethod(); } } @@ -3961,8 +3946,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName, @Nullable ImeTracker.Token statsToken) { - if (mCurClient == null || client == null - || mCurClient.mClient.asBinder() != client.asBinder()) { + // TODO(b/305849394): Get userId from callers. + final int userId = mCurrentUserId; + final var userData = getUserData(userId); + if (userData.mCurClient == null || client == null + || userData.mCurClient.mClient.asBinder() != client.asBinder()) { // We need to check if this is the current client with // focus in the window manager, to allow this call to // be made before input is started in it. @@ -3972,7 +3960,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. throw new IllegalArgumentException("unknown client " + client.asBinder()); } ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN); - if (!isImeClientFocused(mImeBindingState.mFocusedWindow, cs)) { + if (!isImeClientFocused(userData.mImeBindingState.mFocusedWindow, cs)) { Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client)); return false; } @@ -3984,14 +3972,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private boolean canShowInputMethodPickerLocked(IInputMethodClient client) { final int uid = Binder.getCallingUid(); - if (mImeBindingState.mFocusedWindowClient != null && client != null - && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) { + // TODO(b/305849394): Get userId from callers. + final int userId = mCurrentUserId; + final var userData = getUserData(userId); + if (userData.mImeBindingState.mFocusedWindowClient != null && client != null + && userData.mImeBindingState.mFocusedWindowClient.mClient.asBinder() + == client.asBinder()) { return true; } - if (mCurrentUserId != UserHandle.getUserId(uid)) { + if (userId != UserHandle.getUserId(uid)) { return false; } - final var curIntent = getInputMethodBindingController(mCurrentUserId).getCurIntent(); + final var curIntent = getInputMethodBindingController(userId).getCurIntent(); if (curIntent != null && InputMethodUtils.checkIfPackageBelongsToUid( mPackageManagerInternal, uid, curIntent.getComponent().getPackageName())) { return true; @@ -4008,11 +4000,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + Binder.getCallingUid() + ": " + client); return; } - + // TODO(b/305849394): Create a utility method for the following policy. + final int userId = mExperimentalConcurrentMultiUserModeEnabled + ? UserHandle.getCallingUserId() : mCurrentUserId; + final var userData = getUserData(userId); // Always call subtype picker, because subtype picker is a superset of input method // picker. - final int displayId = - (mCurClient != null) ? mCurClient.mSelfReportedDisplayId : DEFAULT_DISPLAY; + final int displayId = (userData.mCurClient != null) + ? userData.mCurClient.mSelfReportedDisplayId : DEFAULT_DISPLAY; mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId) .sendToTarget(); } @@ -4044,33 +4039,31 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @BinderThread - private void setInputMethod(@NonNull IBinder token, String id) { + private void setInputMethod(@NonNull IBinder token, String id, @UserIdInt int userId) { final int callingUid = Binder.getCallingUid(); - final int userId = UserHandle.getUserId(callingUid); synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { return; } - final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final InputMethodInfo imi = settings.getMethodMap().get(id); if (imi == null || !canCallerAccessInputMethod( imi.getPackageName(), callingUid, userId, settings)) { throw getExceptionForUnknownImeId(id); } - setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID); + setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID, userId); } } @BinderThread private void setInputMethodAndSubtype(@NonNull IBinder token, String id, - InputMethodSubtype subtype) { + InputMethodSubtype subtype, @UserIdInt int userId) { final int callingUid = Binder.getCallingUid(); - final int userId = UserHandle.getUserId(callingUid); synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { return; } - final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final InputMethodInfo imi = settings.getMethodMap().get(id); if (imi == null || !canCallerAccessInputMethod( imi.getPackageName(), callingUid, userId, settings)) { @@ -4078,20 +4071,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } if (subtype != null) { setInputMethodWithSubtypeIdLocked(token, id, - SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())); + SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()), userId); } else { - setInputMethod(token, id); + setInputMethod(token, id, userId); } } } @BinderThread - private boolean switchToPreviousInputMethod(@NonNull IBinder token) { + private boolean switchToPreviousInputMethod(@NonNull IBinder token, @UserIdInt int userId) { synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { return false; } - final int userId = mCurrentUserId; final var bindingController = getInputMethodBindingController(userId); final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype(); @@ -4156,9 +4148,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!TextUtils.isEmpty(targetLastImiId)) { if (DEBUG) { Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second - + ", from: " + getSelectedMethodIdLocked() + ", " + subtypeId); + + ", from: " + bindingController.getSelectedMethodId() + ", " + + subtypeId); } - setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId); + setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId, userId); return true; } else { return false; @@ -4167,18 +4160,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @BinderThread - private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme) { + private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme, + @UserIdInt int userId) { synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { return false; } - return switchToNextInputMethodLocked(token, onlyCurrentIme); + return switchToNextInputMethodLocked(token, onlyCurrentIme, userId); } } @GuardedBy("ImfLock.class") - private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) { - final int userId = mCurrentUserId; + private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme, + @UserIdInt int userId) { final var bindingController = getInputMethodBindingController(userId); final var currentImi = bindingController.getSelectedMethod(); final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController @@ -4188,17 +4182,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; } setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(), - nextSubtype.mSubtypeId); + nextSubtype.mSubtypeId, userId); return true; } @BinderThread - private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token) { + private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token, + @UserIdInt int userId) { synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { return false; } - final int userId = mCurrentUserId; final var bindingController = getInputMethodBindingController(userId); final var currentImi = bindingController.getSelectedMethod(); final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController @@ -4260,7 +4254,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(userId, newSettings); if (isCurrentUser) { - postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); + postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, + userId); } } finally { Binder.restoreCallingIdentity(ident); @@ -4299,7 +4294,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mSettingsObserver != null) { mSettingsObserver.mLastEnabled = settings.getEnabledInputMethodsStr(); } - updateInputMethodsFromSettingsLocked(false /* enabledChanged */); + updateInputMethodsFromSettingsLocked(false /* enabledChanged */, userId); } } } finally { @@ -4318,7 +4313,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override @Deprecated public int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) { - int callingUid = Binder.getCallingUid(); + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getCallingUserId(); return Binder.withCleanCallingIdentity(() -> { final int curTokenDisplayId; synchronized (ImfLock.class) { @@ -4326,9 +4322,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. "getInputMethodWindowVisibleHeight", null /* statsToken */)) { return 0; } + // TODO(b/305849394): Create a utility method for the following policy. + final int userId = mExperimentalConcurrentMultiUserModeEnabled + ? callingUserId : mCurrentUserId; + final var bindingController = getInputMethodBindingController(userId); // This should probably use the caller's display id, but because this is unsupported // and maintained only for compatibility, there's no point in fixing it. - curTokenDisplayId = getCurTokenDisplayIdLocked(); + curTokenDisplayId = bindingController.getCurTokenDisplayId(); } return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId); }); @@ -4593,27 +4593,29 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (ImfLock.class) { - final var bindingController = getInputMethodBindingController(mCurrentUserId); + final int userId = mCurrentUserId; + final var bindingController = getInputMethodBindingController(userId); + final var userData = getUserData(userId); final long token = proto.start(fieldId); - proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked()); + proto.write(CUR_METHOD_ID, bindingController.getSelectedMethodId()); proto.write(CUR_SEQ, bindingController.getSequenceNumber()); - proto.write(CUR_CLIENT, Objects.toString(mCurClient)); - mImeBindingState.dumpDebug(proto, mWindowManagerInternal); + proto.write(CUR_CLIENT, Objects.toString(userData.mCurClient)); + userData.mImeBindingState.dumpDebug(proto, mWindowManagerInternal); proto.write(LAST_IME_TARGET_WINDOW_NAME, mWindowManagerInternal.getWindowName(mLastImeTargetWindow)); proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString( - mImeBindingState.mFocusedWindowSoftInputMode)); - if (mCurEditorInfo != null) { - mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE); + userData.mImeBindingState.mFocusedWindowSoftInputMode)); + if (userData.mCurEditorInfo != null) { + userData.mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE); } proto.write(CUR_ID, bindingController.getCurId()); mVisibilityStateComputer.dumpDebug(proto, fieldId); - proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode); - proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked())); - proto.write(CUR_TOKEN_DISPLAY_ID, getCurTokenDisplayIdLocked()); + proto.write(IN_FULLSCREEN_MODE, userData.mInFullscreenMode); + proto.write(CUR_TOKEN, Objects.toString(bindingController.getCurToken())); + proto.write(CUR_TOKEN_DISPLAY_ID, bindingController.getCurTokenDisplayId()); proto.write(SYSTEM_READY, mSystemReady); proto.write(HAVE_CONNECTION, bindingController.hasMainConnection()); - proto.write(BOUND_TO_METHOD, mBoundToMethod); + proto.write(BOUND_TO_METHOD, userData.mBoundToMethod); proto.write(IS_INTERACTIVE, mIsInteractive); proto.write(BACK_DISPOSITION, mBackDisposition); proto.write(IME_WINDOW_VISIBILITY, mImeWindowVis); @@ -4623,20 +4625,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @BinderThread - private void notifyUserAction(@NonNull IBinder token) { + private void notifyUserAction(@NonNull IBinder token, @UserIdInt int userId) { if (DEBUG) { Slog.d(TAG, "Got the notification of a user action."); } synchronized (ImfLock.class) { - if (getCurTokenLocked() != token) { + final var bindingController = getInputMethodBindingController(userId); + if (bindingController.getCurToken() != token) { if (DEBUG) { Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer" + " active."); } return; } - final int userId = mCurrentUserId; - final var bindingController = getInputMethodBindingController(userId); final InputMethodInfo imi = bindingController.getSelectedMethod(); if (imi != null) { getUserData(userId).mSwitchingController.onUserActionLocked(imi, @@ -4647,11 +4648,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible, - @NonNull ImeTracker.Token statsToken) { + @NonNull ImeTracker.Token statsToken, @UserIdInt int userId) { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility"); synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); return; @@ -4659,10 +4660,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom( - windowToken); + windowToken, userId); mVisibilityApplier.applyImeVisibility(requestToken, statsToken, setVisible ? ImeVisibilityStateComputer.STATE_SHOW_IME - : ImeVisibilityStateComputer.STATE_HIDE_IME, mCurrentUserId); + : ImeVisibilityStateComputer.STATE_HIDE_IME, + SoftInputShowHideReason.NOT_SET /* ignore reason */, userId); } } finally { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); @@ -4683,7 +4685,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) { + private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId, + @UserIdInt int userId) { + final var bindingController = getInputMethodBindingController(userId); if (token == null) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.WRITE_SECURE_SETTINGS) @@ -4692,7 +4696,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. "Using null token requires permission " + android.Manifest.permission.WRITE_SECURE_SETTINGS); } - } else if (getCurTokenLocked() != token) { + } else if (bindingController.getCurToken() != token) { Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid() + " token: " + token); return; @@ -4708,7 +4712,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final long ident = Binder.clearCallingIdentity(); try { - setInputMethodLocked(id, subtypeId); + setInputMethodLocked(id, subtypeId, userId); } finally { Binder.restoreCallingIdentity(ident); } @@ -4721,17 +4725,20 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. void onShowHideSoftInputRequested(boolean show, IBinder requestImeToken, @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken, @UserIdInt int userId) { - final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken); + final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken, + userId); final var bindingController = getInputMethodBindingController(userId); + final var userData = getUserData(userId); final WindowManagerInternal.ImeTargetInfo info = mWindowManagerInternal.onToggleImeRequested( - show, mImeBindingState.mFocusedWindow, requestToken, + show, userData.mImeBindingState.mFocusedWindow, requestToken, bindingController.getCurTokenDisplayId()); mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( - mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo, - info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason, - mInFullscreenMode, info.requestWindowName, info.imeControlTargetName, - info.imeLayerTargetName, info.imeSurfaceParentName)); + userData.mImeBindingState.mFocusedWindowClient, + userData.mImeBindingState.mFocusedWindowEditorInfo, + info.focusedWindowName, userData.mImeBindingState.mFocusedWindowSoftInputMode, + reason, userData.mInFullscreenMode, info.requestWindowName, + info.imeControlTargetName, info.imeLayerTargetName, info.imeSurfaceParentName)); if (statsToken != null) { mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName); @@ -4740,30 +4747,33 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread private void hideMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken, - @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) { + @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, + @UserIdInt int userId) { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput"); synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); return; } + final var userData = getUserData(userId); ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); final long ident = Binder.clearCallingIdentity(); try { if (Flags.refactorInsetsController()) { - mCurClient.mClient.setImeVisibility(false); + userData.mCurClient.mClient.setImeVisibility(false); // TODO we will loose the flags here - if (mImeBindingState != null - && mImeBindingState.mFocusedWindowClient != null - && mImeBindingState.mFocusedWindowClient.mClient != null) { - mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(false); + if (userData.mImeBindingState != null + && userData.mImeBindingState.mFocusedWindowClient != null + && userData.mImeBindingState.mFocusedWindowClient.mClient != null) { + userData.mImeBindingState.mFocusedWindowClient.mClient + .setImeVisibility(false); } } else { hideCurrentInputLocked(mLastImeTargetWindow, statsToken, flags, - null /* resultReceiver */, reason); + null /* resultReceiver */, reason, userId); } } finally { Binder.restoreCallingIdentity(ident); @@ -4776,30 +4786,34 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread private void showMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken, - @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) { + @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, + @UserIdInt int userId) { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput"); synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); return; } + final var userData = getUserData(userId); ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); final long ident = Binder.clearCallingIdentity(); try { if (Flags.refactorInsetsController()) { - mCurClient.mClient.setImeVisibility(false); + userData.mCurClient.mClient.setImeVisibility(false); // TODO we will loose the flags here - if (mImeBindingState != null - && mImeBindingState.mFocusedWindowClient != null - && mImeBindingState.mFocusedWindowClient.mClient != null) { - mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(true); + if (userData.mImeBindingState != null + && userData.mImeBindingState.mFocusedWindowClient != null + && userData.mImeBindingState.mFocusedWindowClient.mClient != null) { + userData.mImeBindingState.mFocusedWindowClient.mClient + .setImeVisibility(true); } } else { showCurrentInputLocked(mLastImeTargetWindow, statsToken, flags, - MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason); + MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason, + userId); } } finally { Binder.restoreCallingIdentity(ident); @@ -4810,46 +4824,52 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + @GuardedBy("ImfLock.class") @VisibleForTesting - ImeVisibilityApplier getVisibilityApplier() { - synchronized (ImfLock.class) { - return mVisibilityApplier; - } + DefaultImeVisibilityApplier getVisibilityApplierLocked() { + return mVisibilityApplier; } void onApplyImeVisibilityFromComputer(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result) { synchronized (ImfLock.class) { + // TODO(b/305849394): Infer userId from windowToken + final int userId = mCurrentUserId; mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(), - result.getReason()); + result.getReason(), userId); } } @GuardedBy("ImfLock.class") - void setEnabledSessionLocked(SessionState session) { - if (mEnabledSession != session) { - if (mEnabledSession != null && mEnabledSession.mSession != null) { - if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession); - mEnabledSession.mMethod.setSessionEnabled(mEnabledSession.mSession, false); + void setEnabledSessionLocked(SessionState session, + @NonNull UserDataRepository.UserData userData) { + if (userData.mEnabledSession != session) { + if (userData.mEnabledSession != null && userData.mEnabledSession.mSession != null) { + if (DEBUG) Slog.v(TAG, "Disabling: " + userData.mEnabledSession); + userData.mEnabledSession.mMethod.setSessionEnabled( + userData.mEnabledSession.mSession, false); } - mEnabledSession = session; - if (mEnabledSession != null && mEnabledSession.mSession != null) { - if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession); - mEnabledSession.mMethod.setSessionEnabled(mEnabledSession.mSession, true); + userData.mEnabledSession = session; + if (userData.mEnabledSession != null && userData.mEnabledSession.mSession != null) { + if (DEBUG) Slog.v(TAG, "Enabling: " + userData.mEnabledSession); + userData.mEnabledSession.mMethod.setSessionEnabled( + userData.mEnabledSession.mSession, true); } } } @GuardedBy("ImfLock.class") void setEnabledSessionForAccessibilityLocked( - SparseArray<AccessibilitySessionState> accessibilitySessions) { + SparseArray<AccessibilitySessionState> accessibilitySessions, + @NonNull UserDataRepository.UserData userData) { // mEnabledAccessibilitySessions could the same object as accessibilitySessions. SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>(); - for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) { - if (!accessibilitySessions.contains(mEnabledAccessibilitySessions.keyAt(i))) { - AccessibilitySessionState sessionState = mEnabledAccessibilitySessions.valueAt(i); + for (int i = 0; i < userData.mEnabledAccessibilitySessions.size(); i++) { + if (!accessibilitySessions.contains(userData.mEnabledAccessibilitySessions.keyAt(i))) { + AccessibilitySessionState sessionState = + userData.mEnabledAccessibilitySessions.valueAt(i); if (sessionState != null) { - disabledSessions.append(mEnabledAccessibilitySessions.keyAt(i), + disabledSessions.append(userData.mEnabledAccessibilitySessions.keyAt(i), sessionState.mSession); } } @@ -4860,7 +4880,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } SparseArray<IAccessibilityInputMethodSession> enabledSessions = new SparseArray<>(); for (int i = 0; i < accessibilitySessions.size(); i++) { - if (!mEnabledAccessibilitySessions.contains(accessibilitySessions.keyAt(i))) { + if (!userData.mEnabledAccessibilitySessions.contains(accessibilitySessions.keyAt(i))) { AccessibilitySessionState sessionState = accessibilitySessions.valueAt(i); if (sessionState != null) { enabledSessions.append(accessibilitySessions.keyAt(i), sessionState.mSession); @@ -4871,7 +4891,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. AccessibilityManagerInternal.get().setImeSessionEnabled(enabledSessions, true); } - mEnabledAccessibilitySessions = accessibilitySessions; + userData.mEnabledAccessibilitySessions = accessibilitySessions; } @SuppressWarnings("unchecked") @@ -4931,25 +4951,33 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. case MSG_HIDE_ALL_INPUT_METHODS: synchronized (ImfLock.class) { + // TODO(b/305849394): Needs to figure out what to do where for background users. + final int userId = mCurrentUserId; + final var userData = getUserData(userId); if (Flags.refactorInsetsController()) { - if (mImeBindingState != null - && mImeBindingState.mFocusedWindowClient != null - && mImeBindingState.mFocusedWindowClient.mClient != null) { - mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(false); + if (userData.mImeBindingState != null + && userData.mImeBindingState.mFocusedWindowClient != null + && userData.mImeBindingState.mFocusedWindowClient.mClient != null) { + userData.mImeBindingState.mFocusedWindowClient.mClient + .setImeVisibility(false); } } else { @SoftInputShowHideReason final int reason = (int) msg.obj; - hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, - reason); + hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, + 0 /* flags */, reason, userId); } } return true; case MSG_REMOVE_IME_SURFACE: { synchronized (ImfLock.class) { + // TODO(b/305849394): Needs to figure out what to do where for background users. + final int userId = mCurrentUserId; + final var userData = getUserData(userId); try { - if (mEnabledSession != null && mEnabledSession.mSession != null - && !isShowRequestedForCurrentWindow()) { - mEnabledSession.mSession.removeImeSurface(); + if (userData.mEnabledSession != null + && userData.mEnabledSession.mSession != null + && !isShowRequestedForCurrentWindow(userId)) { + userData.mEnabledSession.mSession.removeImeSurface(); } } catch (RemoteException e) { } @@ -4959,10 +4987,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. case MSG_REMOVE_IME_SURFACE_FROM_WINDOW: { IBinder windowToken = (IBinder) msg.obj; synchronized (ImfLock.class) { + // TODO(b/305849394): Infer userId from windowToken. + final int userId = mCurrentUserId; + final var userData = getUserData(userId); try { - if (windowToken == mImeBindingState.mFocusedWindow - && mEnabledSession != null && mEnabledSession.mSession != null) { - mEnabledSession.mSession.removeImeSurface(); + if (windowToken == userData.mImeBindingState.mFocusedWindow + && userData.mEnabledSession != null + && userData.mEnabledSession.mSession != null) { + userData.mEnabledSession.mSession.removeImeSurface(); } } catch (RemoteException e) { } @@ -5015,9 +5047,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. synchronized (ImfLock.class) { final var bindingController = getInputMethodBindingController(mCurrentUserId); if (bindingController.supportsStylusHandwriting() - && getCurMethodLocked() != null && hasSupportedStylusLocked()) { + && bindingController.getCurMethod() != null + && hasSupportedStylusLocked()) { Slog.d(TAG, "Initializing Handwriting Spy"); - mHwController.initializeHandwritingSpy(getCurTokenDisplayIdLocked()); + mHwController.initializeHandwritingSpy( + bindingController.getCurTokenDisplayId()); } else { mHwController.reset(); } @@ -5034,18 +5068,21 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } return true; case MSG_START_HANDWRITING: + final var handwritingRequest = (HandwritingRequest) msg.obj; synchronized (ImfLock.class) { - IInputMethodInvoker curMethod = getCurMethodLocked(); - if (curMethod == null || mImeBindingState.mFocusedWindow == null) { + final int userId = handwritingRequest.userId; + final var bindingController = getInputMethodBindingController(userId); + final var userData = getUserData(userId); + IInputMethodInvoker curMethod = bindingController.getCurMethod(); + if (curMethod == null || userData.mImeBindingState.mFocusedWindow == null) { return true; } - final var bindingController = getInputMethodBindingController(mCurrentUserId); final HandwritingModeController.HandwritingSession session = mHwController.startHandwritingSession( - msg.arg1 /*requestId*/, - msg.arg2 /*pid*/, + handwritingRequest.requestId, + handwritingRequest.pid, bindingController.getCurMethodUid(), - mImeBindingState.mFocusedWindow); + userData.mImeBindingState.mFocusedWindow); if (session == null) { Slog.e(TAG, "Failed to start handwriting session for requestId: " + msg.arg1); @@ -5080,9 +5117,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return false; } + private record HandwritingRequest(int requestId, int pid, @UserIdInt int userId) { } + @BinderThread - private void onStylusHandwritingReady(int requestId, int pid) { - mHandler.obtainMessage(MSG_START_HANDWRITING, requestId, pid).sendToTarget(); + private void onStylusHandwritingReady(int requestId, int pid, @UserIdInt int userId) { + mHandler.obtainMessage(MSG_START_HANDWRITING, + new HandwritingRequest(requestId, pid, userId)).sendToTarget(); } private void handleSetInteractive(final boolean interactive) { @@ -5090,8 +5130,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mIsInteractive = interactive; updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition); + // TODO(b/305849394): Support multiple IMEs. + final var userId = mCurrentUserId; + final var userData = getUserData(userId); // Inform the current client of the change in active status - if (mCurClient == null || mCurClient.mClient == null) { + if (userData.mCurClient == null || userData.mCurClient.mClient == null) { return; } // TODO(b/325515685): user data must be retrieved by a userId parameter @@ -5101,17 +5144,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Handle IME visibility when interactive changed before finishing the input to // ensure we preserve the last state as possible. final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged( - mImeBindingState.mFocusedWindow, interactive); + userData.mImeBindingState.mFocusedWindow, interactive); if (imeVisRes != null) { // Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker. - mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, - null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason()); + mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow, + null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason(), + userId); } // Eligible IME processes use new "setInteractive" protocol. - mCurClient.mClient.setInteractive(mIsInteractive, mInFullscreenMode); + userData.mCurClient.mClient.setInteractive(mIsInteractive, + userData.mInFullscreenMode); } else { // Legacy IME processes continue using legacy "setActive" protocol. - mCurClient.mClient.setActive(mIsInteractive, mInFullscreenMode); + userData.mCurClient.mClient.setActive(mIsInteractive, userData.mInFullscreenMode); } } } @@ -5228,7 +5273,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme) { + void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme, + @UserIdInt int userId) { if (DEBUG) { Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme + " \n ------ caller=" + Debug.getCallers(10)); @@ -5238,7 +5284,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return; } - final int userId = mCurrentUserId; final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); boolean reenableMinimumNonAuxSystemImes = false; @@ -5291,7 +5336,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!settings.getMethodMap().containsKey(defaultImiId)) { Slog.w(TAG, "Default IME is uninstalled. Choose new default IME."); if (chooseNewDefaultIMELocked()) { - updateInputMethodsFromSettingsLocked(true); + updateInputMethodsFromSettingsLocked(true, userId); } } else { // Double check that the default IME is certainly enabled. @@ -5417,11 +5462,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, - boolean setSubtypeOnly) { - final int userId = mCurrentUserId; + boolean setSubtypeOnly, @UserIdInt int userId) { final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final var bindingController = getInputMethodBindingController(userId); - settings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(), + settings.saveCurrentInputMethodAndSubtypeToHistory(bindingController.getSelectedMethodId(), bindingController.getCurrentSubtype()); // Set Subtype here @@ -5455,11 +5499,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { - final var bindingController = getInputMethodBindingController(mCurrentUserId); + // TODO(b/305849394): get userId from callers + final int userId = mCurrentUserId; + final var bindingController = getInputMethodBindingController(userId); bindingController.setDisplayIdToShowIme(INVALID_DISPLAY); bindingController.setDeviceIdToShowIme(DEVICE_ID_DEFAULT); - final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); settings.putSelectedDefaultDeviceInputMethod(null); InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme); @@ -5476,7 +5522,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } } - setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false); + setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false, userId); } /** @@ -5560,7 +5606,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. .contains(settings.getMethodMap().get(imeId))) { return false; // IME is not found or not enabled. } - setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); + setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID, userId); return true; } if (!settings.getMethodMap().containsKey(imeId) @@ -5602,15 +5648,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - private void switchKeyboardLayoutLocked(int direction) { - final int userId = mCurrentUserId; + private void switchKeyboardLayoutLocked(int direction, @UserIdInt int userId) { final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); - final InputMethodInfo currentImi = settings.getMethodMap().get(getSelectedMethodIdLocked()); + final var bindingController = getInputMethodBindingController(userId); + final InputMethodInfo currentImi = settings.getMethodMap().get( + bindingController.getSelectedMethodId()); if (currentImi == null) { return; } - final var bindingController = getInputMethodBindingController(userId); final InputMethodSubtypeHandle currentSubtypeHandle = InputMethodSubtypeHandle.of(currentImi, bindingController.getCurrentSubtype()); final InputMethodSubtypeHandle nextSubtypeHandle = @@ -5627,7 +5673,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final int subtypeCount = nextImi.getSubtypeCount(); if (subtypeCount == 0) { if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) { - setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID); + setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID, userId); } return; } @@ -5635,7 +5681,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. for (int i = 0; i < subtypeCount; ++i) { if (nextSubtypeHandle.equals( InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) { - setInputMethodLocked(nextImi.getId(), i); + setInputMethodLocked(nextImi.getId(), i, userId); return; } } @@ -5752,7 +5798,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. //TODO(b/150843766): Check if Input Token is valid. final IBinder curHostInputToken; synchronized (ImfLock.class) { - if (displayId != getCurTokenDisplayIdLocked()) { + final var bindingController = getInputMethodBindingController(userId); + if (displayId != bindingController.getCurTokenDisplayId()) { return false; } curHostInputToken = getInputMethodBindingController(userId).getCurHostInputToken(); @@ -5766,7 +5813,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public void reportImeControl(@Nullable IBinder windowToken) { synchronized (ImfLock.class) { - if (mImeBindingState.mFocusedWindow != windowToken) { + // TODO(b/305849394): Need to infer userId or get userId from callers. + final int userId = mCurrentUserId; + final var userData = getUserData(userId); + if (userData.mImeBindingState.mFocusedWindow != windowToken) { // A perceptible value was set for the focused window, but it is no longer in // control, so we reset the perceptible for the window passed as argument. // TODO(b/314149476): Investigate whether this logic is still relevant, if not @@ -5779,10 +5829,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public void onImeParentChanged(int displayId) { synchronized (ImfLock.class) { + // TODO(b/305849394): Need to infer userId or get userId from callers. + final int userId = mCurrentUserId; + final var userData = getUserData(userId); // Hide the IME method menu only when the IME surface parent is changed by the // input target changed, in case seeing the dialog dismiss flickering during // the next focused window starting the input connection. - if (mLastImeTargetWindow != mImeBindingState.mFocusedWindow) { + if (mLastImeTargetWindow != userData.mImeBindingState.mFocusedWindow) { mMenuController.hideInputMethodMenuLocked(); } } @@ -5805,33 +5858,36 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void onSessionForAccessibilityCreated(int accessibilityConnectionId, IAccessibilityInputMethodSession session, @UserIdInt int userId) { synchronized (ImfLock.class) { - final var bindingController = getInputMethodBindingController(mCurrentUserId); + final var bindingController = getInputMethodBindingController(userId); + final var userData = getUserData(userId); // TODO(b/305829876): Implement user ID verification - if (mCurClient != null) { - clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId); - mCurClient.mAccessibilitySessions.put( + if (userData.mCurClient != null) { + clearClientSessionForAccessibilityLocked(userData.mCurClient, + accessibilityConnectionId); + userData.mCurClient.mAccessibilitySessions.put( accessibilityConnectionId, - new AccessibilitySessionState(mCurClient, + new AccessibilitySessionState(userData.mCurClient, accessibilityConnectionId, session)); attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY, - true); + true, userId); - final SessionState sessionState = mCurClient.mCurSession; + final SessionState sessionState = userData.mCurClient.mCurSession; final IInputMethodSession imeSession = sessionState == null ? null : sessionState.mSession; final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = createAccessibilityInputMethodSessions( - mCurClient.mAccessibilitySessions); + userData.mCurClient.mAccessibilitySessions); final InputBindResult res = new InputBindResult( InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION, imeSession, accessibilityInputMethodSessions, /* channel= */ null, bindingController.getCurId(), bindingController.getSequenceNumber(), /* isInputMethodSuppressingSpellChecker= */ false); - mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId); + userData.mCurClient.mClient.onBindAccessibilityService(res, + accessibilityConnectionId); } } } @@ -5840,33 +5896,34 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId, @UserIdInt int userId) { synchronized (ImfLock.class) { - final var bindingController = getInputMethodBindingController(mCurrentUserId); + final var bindingController = getInputMethodBindingController(userId); + final var userData = getUserData(userId); // TODO(b/305829876): Implement user ID verification - if (mCurClient != null) { + if (userData.mCurClient != null) { if (DEBUG) { Slog.v(TAG, "unbindAccessibilityFromCurrentClientLocked: client=" - + mCurClient.mClient.asBinder()); + + userData.mCurClient.mClient.asBinder()); } // A11yManagerService unbinds the disabled accessibility service. We don't need // to do it here. - mCurClient.mClient.onUnbindAccessibilityService( + userData.mCurClient.mClient.onUnbindAccessibilityService( bindingController.getSequenceNumber(), accessibilityConnectionId); } // We only have sessions when we bound to an input method. Remove this session // from all clients. - if (getCurMethodLocked() != null) { + if (bindingController.getCurMethod() != null) { // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. @SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession = c -> clearClientSessionForAccessibilityLocked(c, accessibilityConnectionId); mClientController.forAllClients(clearClientSession); - AccessibilitySessionState session = mEnabledAccessibilitySessions.get( + AccessibilitySessionState session = userData.mEnabledAccessibilitySessions.get( accessibilityConnectionId); if (session != null) { finishSessionForAccessibilityLocked(session); - mEnabledAccessibilitySessions.remove(accessibilityConnectionId); + userData.mEnabledAccessibilitySessions.remove(accessibilityConnectionId); } } } @@ -5883,14 +5940,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void onSwitchKeyboardLayoutShortcut(int direction, int displayId, IBinder targetWindowToken) { synchronized (ImfLock.class) { - switchKeyboardLayoutLocked(direction); + // TODO(b/305849394): Infer userId from displayId + switchKeyboardLayoutLocked(direction, mCurrentUserId); } } } @BinderThread private IInputContentUriToken createInputContentUriToken(@Nullable IBinder token, - @Nullable Uri contentUri, @Nullable String packageName) { + @Nullable Uri contentUri, @Nullable String packageName, @UserIdInt int imeUserId) { if (token == null) { throw new NullPointerException("token"); } @@ -5907,15 +5965,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. synchronized (ImfLock.class) { final int uid = Binder.getCallingUid(); - final int imeUserId = UserHandle.getUserId(uid); - if (imeUserId != mCurrentUserId) { - // Currently concurrent multi-user is not supported here due to the remaining - // dependency on mCurEditorInfo and mCurClient. - // TODO(b/341558132): Remove this early-exit once it becomes multi-user ready. - Slog.i(TAG, "Ignoring createInputContentUriToken due to user ID mismatch." - + " imeUserId=" + imeUserId + " mCurrentUserId=" + mCurrentUserId); - return null; - } final var bindingController = getInputMethodBindingController(imeUserId); if (bindingController.getSelectedMethodId() == null) { return null; @@ -5928,16 +5977,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // We cannot simply distinguish a bad IME that reports an arbitrary package name from // an unfortunate IME whose internal state is already obsolete due to the asynchronous // nature of our system. Let's compare it with our internal record. - // TODO(b/341558132): Use "imeUserId" to query per-user "curEditorInfo" - final var curPackageName = mCurEditorInfo != null ? mCurEditorInfo.packageName : null; + final var userData = getUserData(imeUserId); + final var curPackageName = userData.mCurEditorInfo != null + ? userData.mCurEditorInfo.packageName : null; if (!TextUtils.equals(curPackageName, packageName)) { Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName=" + curPackageName + " packageName=" + packageName); return null; } // This user ID can never be spoofed. - // TODO(b/341558132): Use "imeUserId" to query per-user "curClient" - final int appUserId = UserHandle.getUserId(mCurClient.mUid); + final int appUserId = UserHandle.getUserId(userData.mCurClient.mUid); // This user ID may be invalid if "contentUri" embedded an invalid user ID. final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri, imeUserId); @@ -5954,14 +6003,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @BinderThread - private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen) { + private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen, + @UserIdInt int userId) { synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(token)) { + if (!calledWithValidTokenLocked(token, userId)) { return; } - if (mCurClient != null && mCurClient.mClient != null) { - mInFullscreenMode = fullscreen; - mCurClient.mClient.reportFullscreenMode(fullscreen); + final var userData = getUserData(userId); + if (userData.mCurClient != null && userData.mCurClient.mClient != null) { + userData.mInFullscreenMode = fullscreen; + userData.mCurClient.mClient.reportFullscreenMode(fullscreen); } } } @@ -6054,7 +6105,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final Printer p = new PrintWriterPrinter(pw); synchronized (ImfLock.class) { - final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final int userId = mCurrentUserId; + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); + final var userData = getUserData(userId); p.println("Current Input Method Manager state:"); final List<InputMethodInfo> methodList = settings.getMethodList(); int numImes = methodList.size(); @@ -6084,16 +6137,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mClientController.forAllClients(clientControllerDump); final var bindingController = getInputMethodBindingController(mCurrentUserId); p.println(" mCurrentUserId=" + mCurrentUserId); - p.println(" mCurMethodId=" + getSelectedMethodIdLocked()); - client = mCurClient; + p.println(" mCurMethodId=" + bindingController.getSelectedMethodId()); + client = userData.mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" + bindingController.getSequenceNumber()); p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible); - mImeBindingState.dump(/* prefix= */ " ", p); + userData.mImeBindingState.dump(/* prefix= */ " ", p); p.println(" mCurId=" + bindingController.getCurId() + " mHaveConnection=" + bindingController.hasMainConnection() - + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + + " mBoundToMethod=" + userData.mBoundToMethod + " mVisibleBound=" + bindingController.isVisibleBound()); p.println(" mUserDataRepository="); @@ -6109,15 +6162,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. }; mUserDataRepository.forAllUserData(userDataDump); - p.println(" mCurToken=" + getCurTokenLocked()); - p.println(" mCurTokenDisplayId=" + getCurTokenDisplayIdLocked()); + p.println(" mCurToken=" + bindingController.getCurToken()); + p.println(" mCurTokenDisplayId=" + bindingController.getCurTokenDisplayId()); p.println(" mCurHostInputToken=" + bindingController.getCurHostInputToken()); p.println(" mCurIntent=" + bindingController.getCurIntent()); - method = getCurMethodLocked(); - p.println(" mCurMethod=" + getCurMethodLocked()); - p.println(" mEnabledSession=" + mEnabledSession); + method = bindingController.getCurMethod(); + p.println(" mCurMethod=" + method); + p.println(" mEnabledSession=" + userData.mEnabledSession); mVisibilityStateComputer.dump(pw, " "); - p.println(" mInFullscreenMode=" + mInFullscreenMode); + p.println(" mInFullscreenMode=" + userData.mInFullscreenMode); p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive); p.println(" mExperimentalConcurrentMultiUserModeEnabled=" + mExperimentalConcurrentMultiUserModeEnabled); @@ -6153,20 +6206,24 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } else { p.println("No input method client."); } - - if (mImeBindingState.mFocusedWindowClient != null - && client != mImeBindingState.mFocusedWindowClient) { - p.println(" "); - p.println("Warning: Current input method client doesn't match the last focused. " - + "window."); - p.println("Dumping input method client in the last focused window just in case."); - p.println(" "); - pw.flush(); - try { - TransferPipe.dumpAsync( - mImeBindingState.mFocusedWindowClient.mClient.asBinder(), fd, args); - } catch (IOException | RemoteException e) { - p.println("Failed to dump input method client in focused window: " + e); + synchronized (ImfLock.class) { + final int userId = mCurrentUserId; + final var userData = getUserData(userId); + if (userData.mImeBindingState.mFocusedWindowClient != null + && client != userData.mImeBindingState.mFocusedWindowClient) { + p.println(" "); + p.println("Warning: Current input method client doesn't match the last focused. " + + "window."); + p.println("Dumping input method client in the last focused window just in case."); + p.println(" "); + pw.flush(); + try { + TransferPipe.dumpAsync( + userData.mImeBindingState.mFocusedWindowClient.mClient.asBinder(), fd, + args); + } catch (IOException | RemoteException e) { + p.println("Failed to dump input method client in focused window: " + e); + } } } @@ -6602,18 +6659,21 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final List<InputMethodInfo> nextEnabledImes; final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); if (userId == mCurrentUserId) { + final var userData = getUserData(userId); if (Flags.refactorInsetsController()) { - if (mImeBindingState != null - && mImeBindingState.mFocusedWindowClient != null - && mImeBindingState.mFocusedWindowClient.mClient != null) { - mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility( - false); + if (userData.mImeBindingState != null + && userData.mImeBindingState.mFocusedWindowClient != null + && userData.mImeBindingState.mFocusedWindowClient.mClient + != null) { + userData.mImeBindingState.mFocusedWindowClient.mClient + .setImeVisibility(false); } else { // TODO(b329229469): ImeTracker? } } else { - hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, - SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); + hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, + 0 /* flags */, + SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND, userId); } final var bindingController = getInputMethodBindingController(userId); bindingController.unbindCurrentMethod(); @@ -6633,7 +6693,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (!chooseNewDefaultIMELocked()) { resetSelectedInputMethodAndSubtypeLocked(null); } - updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); + updateInputMethodsFromSettingsLocked(true /* enabledMayChange */, userId); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, settings.getUserId()), settings.getEnabledInputMethodList()); @@ -6742,13 +6802,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * @param reason the reason why the IME request was created */ @NonNull + @GuardedBy("ImfLock.class") private ImeTracker.Token createStatsTokenForFocusedClient(boolean show, - @SoftInputShowHideReason int reason) { - final int uid = mImeBindingState.mFocusedWindowClient != null - ? mImeBindingState.mFocusedWindowClient.mUid + @SoftInputShowHideReason int reason, @UserIdInt int userId) { + final var userData = getUserData(userId); + final int uid = userData.mImeBindingState.mFocusedWindowClient != null + ? userData.mImeBindingState.mFocusedWindowClient.mUid : -1; - final var packageName = mImeBindingState.mFocusedWindowEditorInfo != null - ? mImeBindingState.mFocusedWindowEditorInfo.packageName + final var packageName = userData.mImeBindingState.mFocusedWindowEditorInfo != null + ? userData.mImeBindingState.mFocusedWindowEditorInfo.packageName : "uid(" + uid + ")"; return ImeTracker.forLogging().onStart(packageName, uid, @@ -6761,23 +6823,26 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private final InputMethodManagerService mImms; @NonNull private final IBinder mToken; + @UserIdInt + private final int mUserId; InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms, - @NonNull IBinder token) { + @NonNull IBinder token, @UserIdInt int userId) { mImms = imms; mToken = token; + mUserId = userId; } @BinderThread @Override public void setImeWindowStatusAsync(int vis, int backDisposition) { - mImms.setImeWindowStatus(mToken, vis, backDisposition); + mImms.setImeWindowStatus(mToken, vis, backDisposition, mUserId); } @BinderThread @Override public void reportStartInputAsync(IBinder startInputToken) { - mImms.reportStartInput(mToken, startInputToken); + mImms.reportStartInput(mToken, startInputToken, mUserId); } @BinderThread @@ -6793,7 +6858,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future; try { typedFuture.complete(mImms.createInputContentUriToken( - mToken, contentUri, packageName).asBinder()); + mToken, contentUri, packageName, mUserId).asBinder()); } catch (Throwable e) { typedFuture.completeExceptionally(e); } @@ -6802,7 +6867,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread @Override public void reportFullscreenModeAsync(boolean fullscreen) { - mImms.reportFullscreenMode(mToken, fullscreen); + mImms.reportFullscreenMode(mToken, fullscreen, mUserId); } @BinderThread @@ -6810,7 +6875,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void setInputMethod(String id, AndroidFuture future /* T=Void */) { @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { - mImms.setInputMethod(mToken, id); + mImms.setInputMethod(mToken, id, mUserId); typedFuture.complete(null); } catch (Throwable e) { typedFuture.completeExceptionally(e); @@ -6823,7 +6888,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. AndroidFuture future /* T=Void */) { @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { - mImms.setInputMethodAndSubtype(mToken, id, subtype); + mImms.setInputMethodAndSubtype(mToken, id, subtype, mUserId); typedFuture.complete(null); } catch (Throwable e) { typedFuture.completeExceptionally(e); @@ -6837,7 +6902,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. AndroidFuture future /* T=Void */) { @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { - mImms.hideMySoftInput(mToken, statsToken, flags, reason); + mImms.hideMySoftInput(mToken, statsToken, flags, reason, mUserId); typedFuture.complete(null); } catch (Throwable e) { typedFuture.completeExceptionally(e); @@ -6851,7 +6916,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. AndroidFuture future /* T=Void */) { @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { - mImms.showMySoftInput(mToken, statsToken, flags, reason); + mImms.showMySoftInput(mToken, statsToken, flags, reason, mUserId); typedFuture.complete(null); } catch (Throwable e) { typedFuture.completeExceptionally(e); @@ -6861,7 +6926,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread @Override public void updateStatusIconAsync(String packageName, @DrawableRes int iconId) { - mImms.updateStatusIcon(mToken, packageName, iconId); + mImms.updateStatusIcon(mToken, packageName, iconId, mUserId); } @BinderThread @@ -6869,7 +6934,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) { @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future; try { - typedFuture.complete(mImms.switchToPreviousInputMethod(mToken)); + typedFuture.complete(mImms.switchToPreviousInputMethod(mToken, mUserId)); } catch (Throwable e) { typedFuture.completeExceptionally(e); } @@ -6881,7 +6946,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. AndroidFuture future /* T=Boolean */) { @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future; try { - typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme)); + typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme, + mUserId)); } catch (Throwable e) { typedFuture.completeExceptionally(e); } @@ -6892,7 +6958,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) { @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future; try { - typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken)); + typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken, mUserId)); } catch (Throwable e) { typedFuture.completeExceptionally(e); } @@ -6901,20 +6967,20 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread @Override public void notifyUserActionAsync() { - mImms.notifyUserAction(mToken); + mImms.notifyUserAction(mToken, mUserId); } @BinderThread @Override public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible, @NonNull ImeTracker.Token statsToken) { - mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken); + mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken, mUserId); } @BinderThread @Override public void onStylusHandwritingReady(int requestId, int pid) { - mImms.onStylusHandwritingReady(requestId, pid); + mImms.onStylusHandwritingReady(requestId, pid, mUserId); } @BinderThread @@ -6927,12 +6993,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public void switchKeyboardLayoutAsync(int direction) { synchronized (ImfLock.class) { - if (!mImms.calledWithValidTokenLocked(mToken)) { + if (!mImms.calledWithValidTokenLocked(mToken, mUserId)) { return; } final long ident = Binder.clearCallingIdentity(); try { - mImms.switchKeyboardLayoutLocked(direction); + mImms.switchKeyboardLayoutLocked(direction, mUserId); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 326ef7e055fa..89a31e7a9a0b 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -79,6 +79,7 @@ final class InputMethodMenuController { if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes); final int userId = mService.getCurrentImeUserIdLocked(); + final var bindingController = mService.getInputMethodBindingController(userId); hideInputMethodMenuLocked(); @@ -86,9 +87,9 @@ final class InputMethodMenuController { final InputMethodSubtype currentSubtype = mService.getCurrentInputMethodSubtypeLocked(); if (currentSubtype != null) { - final String curMethodId = mService.getSelectedMethodIdLocked(); + final String curMethodId = bindingController.getSelectedMethodId(); final InputMethodInfo currentImi = - mService.queryInputMethodForCurrentUserLocked(curMethodId); + InputMethodSettingsRepository.get(userId).getMethodMap().get(curMethodId); preferredInputMethodSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode( currentImi, currentSubtype.hashCode()); } @@ -179,7 +180,7 @@ final class InputMethodMenuController { if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) { subtypeId = NOT_A_SUBTYPE_ID; } - mService.setInputMethodLocked(im.getId(), subtypeId); + mService.setInputMethodLocked(im.getId(), subtypeId, userId); } hideInputMethodMenuLocked(); } diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java index 3da4a14b10be..48284fb3163c 100644 --- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java +++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java @@ -17,12 +17,18 @@ package com.android.server.inputmethod; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.UserInfo; import android.os.Handler; import android.util.SparseArray; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ImeTracker; +import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.annotations.GuardedBy; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; +import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.server.pm.UserManagerInternal; import java.util.function.Consumer; @@ -96,6 +102,78 @@ final class UserDataRepository { final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController; /** + * Have we called mCurMethod.bindInput()? + */ + @GuardedBy("ImfLock.class") + boolean mBoundToMethod = false; + + /** + * Have we called bindInput() for accessibility services? + */ + @GuardedBy("ImfLock.class") + boolean mBoundToAccessibility; + + @GuardedBy("ImfLock.class") + @NonNull + ImeBindingState mImeBindingState = ImeBindingState.newEmptyState(); + + @GuardedBy("ImfLock.class") + @Nullable + ClientState mCurClient = null; + + @GuardedBy("ImfLock.class") + boolean mInFullscreenMode; + + /** + * The {@link IRemoteInputConnection} last provided by the current client. + */ + @GuardedBy("ImfLock.class") + @Nullable + IRemoteInputConnection mCurInputConnection; + + /** + * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to + * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME. + */ + @GuardedBy("ImfLock.class") + @Nullable + ImeOnBackInvokedDispatcher mCurImeDispatcher; + + /** + * The {@link IRemoteAccessibilityInputConnection} last provided by the current client. + */ + @GuardedBy("ImfLock.class") + @Nullable + IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection; + + /** + * The {@link EditorInfo} last provided by the current client. + */ + @GuardedBy("ImfLock.class") + @Nullable + EditorInfo mCurEditorInfo; + + /** + * The token tracking the current IME show request that is waiting for a connection to an + * IME, otherwise {@code null}. + */ + @GuardedBy("ImfLock.class") + @Nullable + ImeTracker.Token mCurStatsToken; + + /** + * Currently enabled session. + */ + @GuardedBy("ImfLock.class") + @Nullable + InputMethodManagerService.SessionState mEnabledSession; + + @GuardedBy("ImfLock.class") + @Nullable + SparseArray<InputMethodManagerService.AccessibilitySessionState> + mEnabledAccessibilitySessions = new SparseArray<>(); + + /** * Intended to be instantiated only from this file. */ private UserData(@UserIdInt int userId, diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java index 757c07c0b683..41aac32ad08f 100644 --- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java +++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java @@ -254,7 +254,7 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback { synchronized (ImfLock.class) { ClientState cs = imms.getClientStateLocked(client); if (cs != null) { - imms.requestClientSessionLocked(cs); + imms.requestClientSessionLocked(cs, userId); imms.requestClientSessionForAccessibilityLocked(cs); } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 7a722bc914f7..a0aad5215f00 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -81,7 +81,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.PriorityQueue; -import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -162,8 +161,8 @@ public class ContextHubService extends IContextHubService.Stub { new PriorityQueue<>( Comparator.comparingLong(ReliableMessageRecord::getTimestamp)); - // The test mode manager that manages behaviors during test mode. - private final TestModeManager mTestModeManager = new TestModeManager(); + // The test mode manager that manages behaviors during test mode + private final ContextHubTestModeManager mTestModeManager = new ContextHubTestModeManager(); // The period of the recurring time private static final int PERIOD_METRIC_QUERY_DAYS = 1; @@ -226,17 +225,20 @@ public class ContextHubService extends IContextHubService.Stub { @Override public void handleNanoappMessage(short hostEndpointId, NanoAppMessage message, List<String> nanoappPermissions, List<String> messagePermissions) { - if (Flags.reliableMessageImplementation() + // Only process the message normally if not using test mode manager or if + // the test mode manager call returned false as this indicates it did not + // process the message. + boolean useTestModeManager = Flags.reliableMessageImplementation() && Flags.reliableMessageTestModeBehavior() - && mIsTestModeEnabled.get() - && mTestModeManager.handleNanoappMessage(mContextHubId, hostEndpointId, - message, nanoappPermissions, messagePermissions)) { - // The TestModeManager handled the nanoapp message, so return here. - return; + && mIsTestModeEnabled.get(); + if (!useTestModeManager + || !mTestModeManager.handleNanoappMessage(() -> { + handleClientMessageCallback(mContextHubId, hostEndpointId, + message, nanoappPermissions, messagePermissions); + }, message)) { + handleClientMessageCallback(mContextHubId, hostEndpointId, + message, nanoappPermissions, messagePermissions); } - - handleClientMessageCallback(mContextHubId, hostEndpointId, message, - nanoappPermissions, messagePermissions); } @Override @@ -261,8 +263,6 @@ public class ContextHubService extends IContextHubService.Stub { * Records a reliable message from a nanoapp for duplicate detection. */ private static class ReliableMessageRecord { - public static final int TIMEOUT_NS = 1000000000; - public int mContextHubId; public long mTimestamp; public int mMessageSequenceNumber; @@ -297,56 +297,8 @@ public class ContextHubService extends IContextHubService.Stub { } public boolean isExpired() { - return mTimestamp + TIMEOUT_NS < SystemClock.elapsedRealtimeNanos(); - } - } - - /** - * A class to manage behaviors during test mode. This is used for testing. - */ - private class TestModeManager { - /** - * Probability (in percent) of duplicating a message. - */ - private static final int MESSAGE_DUPLICATION_PROBABILITY_PERCENT = 50; - - /** - * The number of total messages to send when the duplicate event happens. - */ - private static final int NUM_MESSAGES_TO_DUPLICATE = 3; - - /** - * A probability percent for a certain event. - */ - private static final int MAX_PROBABILITY_PERCENT = 100; - - private final Random mRandom = new Random(); - - /** - * @return whether the message was handled - * @see ContextHubServiceCallback#handleNanoappMessage - */ - public boolean handleNanoappMessage(int contextHubId, - short hostEndpointId, NanoAppMessage message, - List<String> nanoappPermissions, List<String> messagePermissions) { - if (!message.isReliable()) { - return false; - } - - if (Flags.reliableMessageDuplicateDetectionService() - && mRandom.nextInt(MAX_PROBABILITY_PERCENT) - < MESSAGE_DUPLICATION_PROBABILITY_PERCENT) { - Log.i(TAG, "[TEST MODE] Duplicating message (" - + NUM_MESSAGES_TO_DUPLICATE - + " sends) with message sequence number: " - + message.getMessageSequenceNumber()); - for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) { - handleClientMessageCallback(contextHubId, hostEndpointId, - message, nanoappPermissions, messagePermissions); - } - return true; - } - return false; + return mTimestamp + ContextHubTransactionManager.RELIABLE_MESSAGE_TIMEOUT.toNanos() + < SystemClock.elapsedRealtimeNanos(); } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java index 6da7a6500d54..2ec9bdb75349 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java @@ -27,53 +27,65 @@ import java.util.concurrent.TimeUnit; * * @hide */ -/* package */ abstract class ContextHubServiceTransaction { +abstract class ContextHubServiceTransaction { private final int mTransactionId; + @ContextHubTransaction.Type private final int mTransactionType; - /** The ID of the nanoapp this transaction is targeted for, null if not applicable. */ private final Long mNanoAppId; - /** - * The host package associated with this transaction. - */ private final String mPackage; - /** - * The message sequence number associated with this transaction, null if not applicable. - */ private final Integer mMessageSequenceNumber; - /** - * true if the transaction has already completed, false otherwise - */ + private long mNextRetryTime; + + private long mTimeoutTime; + + /** The number of times the transaction has been started (start function called). */ + private int mNumCompletedStartCalls; + + private final short mHostEndpointId; + private boolean mIsComplete = false; - /* package */ ContextHubServiceTransaction(int id, int type, String packageName) { + ContextHubServiceTransaction(int id, int type, String packageName) { mTransactionId = id; mTransactionType = type; mNanoAppId = null; mPackage = packageName; mMessageSequenceNumber = null; + mNextRetryTime = Long.MAX_VALUE; + mTimeoutTime = Long.MAX_VALUE; + mNumCompletedStartCalls = 0; + mHostEndpointId = Short.MAX_VALUE; } - /* package */ ContextHubServiceTransaction(int id, int type, long nanoAppId, + ContextHubServiceTransaction(int id, int type, long nanoAppId, String packageName) { mTransactionId = id; mTransactionType = type; mNanoAppId = nanoAppId; mPackage = packageName; mMessageSequenceNumber = null; + mNextRetryTime = Long.MAX_VALUE; + mTimeoutTime = Long.MAX_VALUE; + mNumCompletedStartCalls = 0; + mHostEndpointId = Short.MAX_VALUE; } - /* package */ ContextHubServiceTransaction(int id, int type, String packageName, - int messageSequenceNumber) { + ContextHubServiceTransaction(int id, int type, String packageName, + int messageSequenceNumber, short hostEndpointId) { mTransactionId = id; mTransactionType = type; mNanoAppId = null; mPackage = packageName; mMessageSequenceNumber = messageSequenceNumber; + mNextRetryTime = Long.MAX_VALUE; + mTimeoutTime = Long.MAX_VALUE; + mNumCompletedStartCalls = 0; + mHostEndpointId = hostEndpointId; } /** @@ -95,7 +107,7 @@ import java.util.concurrent.TimeUnit; * * @param result the result of the transaction */ - /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) { + void onTransactionComplete(@ContextHubTransaction.Result int result) { } /** @@ -106,44 +118,51 @@ import java.util.concurrent.TimeUnit; * @param result the result of the query * @param nanoAppStateList the list of nanoapps given by the query response */ - /* package */ void onQueryResponse( + void onQueryResponse( @ContextHubTransaction.Result int result, List<NanoAppState> nanoAppStateList) { } - /** - * @return the ID of this transaction - */ - /* package */ int getTransactionId() { + int getTransactionId() { return mTransactionId; } - /** - * @return the type of this transaction - * @see ContextHubTransaction.Type - */ @ContextHubTransaction.Type - /* package */ int getTransactionType() { + int getTransactionType() { return mTransactionType; } - /** - * @return the message sequence number of this transaction - */ Integer getMessageSequenceNumber() { return mMessageSequenceNumber; } + long getNextRetryTime() { + return mNextRetryTime; + } + + long getTimeoutTime() { + return mTimeoutTime; + } + + int getNumCompletedStartCalls() { + return mNumCompletedStartCalls; + } + + short getHostEndpointId() { + return mHostEndpointId; + } + /** * Gets the timeout period as defined in IContexthub.hal * * @return the timeout of this transaction in the specified time unit */ - /* package */ long getTimeout(TimeUnit unit) { + long getTimeout(TimeUnit unit) { switch (mTransactionType) { case ContextHubTransaction.TYPE_LOAD_NANOAPP: return unit.convert(30L, TimeUnit.SECONDS); case ContextHubTransaction.TYPE_RELIABLE_MESSAGE: - return unit.convert(1000L, TimeUnit.MILLISECONDS); + return unit.convert(ContextHubTransactionManager.RELIABLE_MESSAGE_TIMEOUT.toNanos(), + TimeUnit.NANOSECONDS); case ContextHubTransaction.TYPE_UNLOAD_NANOAPP: case ContextHubTransaction.TYPE_ENABLE_NANOAPP: case ContextHubTransaction.TYPE_DISABLE_NANOAPP: @@ -159,14 +178,23 @@ import java.util.concurrent.TimeUnit; * * Should only be called as a result of a response from a Context Hub callback */ - /* package */ void setComplete() { + void setComplete() { mIsComplete = true; } - /** - * @return true if the transaction has already completed, false otherwise - */ - /* package */ boolean isComplete() { + void setNextRetryTime(long nextRetryTime) { + mNextRetryTime = nextRetryTime; + } + + void setTimeoutTime(long timeoutTime) { + mTimeoutTime = timeoutTime; + } + + void setNumCompletedStartCalls(int numCompletedStartCalls) { + mNumCompletedStartCalls = numCompletedStartCalls; + } + + boolean isComplete() { return mIsComplete; } @@ -187,7 +215,18 @@ import java.util.concurrent.TimeUnit; out.append(", messageSequenceNumber = "); out.append(mMessageSequenceNumber); } + if (mTransactionType == ContextHubTransaction.TYPE_RELIABLE_MESSAGE) { + out.append(", nextRetryTime = "); + out.append(mNextRetryTime); + out.append(", timeoutTime = "); + out.append(mTimeoutTime); + out.append(", numCompletedStartCalls = "); + out.append(mNumCompletedStartCalls); + out.append(", hostEndpointId = "); + out.append(mHostEndpointId); + } out.append(")"); + return out.toString(); } } diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java new file mode 100644 index 000000000000..e50324eb7c83 --- /dev/null +++ b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.contexthub; + +import android.chre.flags.Flags; +import android.hardware.location.NanoAppMessage; +import android.util.Log; + +import java.util.Random; + +/** + * A class to manage behaviors during test mode. This is used for testing. + * @hide + */ +public class ContextHubTestModeManager { + private static final String TAG = "ContextHubTestModeManager"; + + /** Probability (in percent) of duplicating a message. */ + private static final int MESSAGE_DROP_PROBABILITY_PERCENT = 20; + + /** Probability (in percent) of duplicating a message. */ + private static final int MESSAGE_DUPLICATION_PROBABILITY_PERCENT = 20; + + /** The number of total messages to send when the duplicate event happens. */ + private static final int NUM_MESSAGES_TO_DUPLICATE = 3; + + /** A probability percent for a certain event. */ + private static final int MAX_PROBABILITY_PERCENT = 100; + + private final Random mRandom = new Random(); + + /** + * @return whether the message was handled + * @see ContextHubServiceCallback#handleNanoappMessage + */ + public boolean handleNanoappMessage(Runnable handleMessage, NanoAppMessage message) { + if (Flags.reliableMessageDuplicateDetectionService() + && message.isReliable() + && mRandom.nextInt(MAX_PROBABILITY_PERCENT) + < MESSAGE_DUPLICATION_PROBABILITY_PERCENT) { + Log.i(TAG, "[TEST MODE] Duplicating message (" + + NUM_MESSAGES_TO_DUPLICATE + + " sends) with message sequence number: " + + message.getMessageSequenceNumber()); + for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) { + handleMessage.run(); + } + return true; + } + return false; + } + + /** + * @return whether the message was handled + * @see IContextHubWrapper#sendMessageToContextHub + */ + public boolean sendMessageToContextHub(NanoAppMessage message) { + if (Flags.reliableMessageRetrySupportService() + && message.isReliable() + && mRandom.nextInt(MAX_PROBABILITY_PERCENT) + < MESSAGE_DROP_PROBABILITY_PERCENT) { + Log.i(TAG, "[TEST MODE] Dropping message with message sequence number: " + + message.getMessageSequenceNumber()); + return true; + } + return false; + } +} diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java index ec94e2be2c59..1a449e024ee1 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java @@ -16,19 +16,26 @@ package com.android.server.location.contexthub; +import android.chre.flags.Flags; import android.hardware.location.ContextHubTransaction; import android.hardware.location.IContextHubTransactionCallback; import android.hardware.location.NanoAppBinary; import android.hardware.location.NanoAppMessage; import android.hardware.location.NanoAppState; import android.os.RemoteException; +import android.os.SystemClock; import android.util.Log; +import java.time.Duration; import java.util.ArrayDeque; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Random; +import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -47,34 +54,30 @@ import java.util.concurrent.atomic.AtomicInteger; /* package */ class ContextHubTransactionManager { private static final String TAG = "ContextHubTransactionManager"; - /* - * Maximum number of transaction requests that can be pending at a time - */ + public static final Duration RELIABLE_MESSAGE_TIMEOUT = Duration.ofSeconds(1); + private static final int MAX_PENDING_REQUESTS = 10000; - /* - * The proxy to talk to the Context Hub - */ + private static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3; + + private static final Duration RELIABLE_MESSAGE_RETRY_WAIT_TIME = Duration.ofMillis(250); + + private static final Duration RELIABLE_MESSAGE_MIN_WAIT_TIME = Duration.ofNanos(1000); + private final IContextHubWrapper mContextHubProxy; - /* - * The manager for all clients for the service. - */ private final ContextHubClientManager mClientManager; - /* - * The nanoapp state manager for the service - */ private final NanoAppStateManager mNanoAppStateManager; - /* - * A queue containing the current transactions - */ private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>(); - /* - * The next available transaction ID - */ + private final Map<Integer, ContextHubServiceTransaction> mReliableMessageTransactionMap = + new HashMap<>(); + + /** A set of host endpoint IDs that have an active pending transaction. */ + private final Set<Short> mReliableMessageHostEndpointIdActiveSet = new HashSet<>(); + private final AtomicInteger mNextAvailableId = new AtomicInteger(); /** @@ -86,10 +89,12 @@ import java.util.concurrent.atomic.AtomicInteger; new AtomicInteger(new Random().nextInt(Integer.MAX_VALUE / 2)); /* - * An executor and the future object for scheduling timeout timers + * An executor and the future object for scheduling timeout timers and + * for scheduling the processing of reliable message transactions. */ - private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1); + private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1); private ScheduledFuture<?> mTimeoutFuture = null; + private ScheduledFuture<?> mReliableMessageTransactionFuture = null; /* * The list of previous transaction records. @@ -333,7 +338,7 @@ import java.util.concurrent.atomic.AtomicInteger; IContextHubTransactionCallback transactionCallback, String packageName) { return new ContextHubServiceTransaction(mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_RELIABLE_MESSAGE, packageName, - mNextAvailableMessageSequenceNumber.getAndIncrement()) { + mNextAvailableMessageSequenceNumber.getAndIncrement(), hostEndpointId) { @Override /* package */ int onTransact() { try { @@ -416,16 +421,23 @@ import java.util.concurrent.atomic.AtomicInteger; return; } - if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) { + if (mTransactionQueue.size() >= MAX_PENDING_REQUESTS + || mReliableMessageTransactionMap.size() >= MAX_PENDING_REQUESTS) { throw new IllegalStateException("Transaction queue is full (capacity = " + MAX_PENDING_REQUESTS + ")"); } - mTransactionQueue.add(transaction); mTransactionRecordDeque.add(new TransactionRecord(transaction.toString())); - - if (mTransactionQueue.size() == 1) { - startNextTransaction(); + if (Flags.reliableMessageRetrySupportService() + && transaction.getTransactionType() + == ContextHubTransaction.TYPE_RELIABLE_MESSAGE) { + mReliableMessageTransactionMap.put(transaction.getMessageSequenceNumber(), transaction); + mExecutor.execute(() -> processMessageTransactions()); + } else { + mTransactionQueue.add(transaction); + if (mTransactionQueue.size() == 1) { + startNextTransaction(); + } } } @@ -455,26 +467,42 @@ import java.util.concurrent.atomic.AtomicInteger; /* package */ synchronized void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) { - ContextHubServiceTransaction transaction = mTransactionQueue.peek(); - if (transaction == null) { - Log.w(TAG, "Received unexpected transaction response (no transaction pending)"); + if (!Flags.reliableMessageRetrySupportService()) { + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); + if (transaction == null) { + Log.w(TAG, "Received unexpected transaction response (no transaction pending)"); + return; + } + + Integer transactionMessageSequenceNumber = transaction.getMessageSequenceNumber(); + if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE + || transactionMessageSequenceNumber == null + || transactionMessageSequenceNumber != messageSequenceNumber) { + Log.w(TAG, "Received unexpected message transaction response (expected message " + + "sequence number = " + + transaction.getMessageSequenceNumber() + + ", received messageSequenceNumber = " + messageSequenceNumber + ")"); + return; + } + + transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS : + ContextHubTransaction.RESULT_FAILED_AT_HUB); + removeTransactionAndStartNext(); return; } - Integer transactionMessageSequenceNumber = transaction.getMessageSequenceNumber(); - if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE - || transactionMessageSequenceNumber == null - || transactionMessageSequenceNumber != messageSequenceNumber) { - Log.w(TAG, "Received unexpected message transaction response (expected message " - + "sequence number = " - + transaction.getMessageSequenceNumber() - + ", received messageSequenceNumber = " + messageSequenceNumber + ")"); + ContextHubServiceTransaction transaction = + mReliableMessageTransactionMap.get(messageSequenceNumber); + if (transaction == null) { + Log.w(TAG, "Could not find reliable message transaction with message sequence number" + + messageSequenceNumber); return; } - transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS : - ContextHubTransaction.RESULT_FAILED_AT_HUB); - removeTransactionAndStartNext(); + completeMessageTransaction(transaction, + success ? ContextHubTransaction.RESULT_SUCCESS + : ContextHubTransaction.RESULT_FAILED_AT_HUB); + mExecutor.execute(() -> processMessageTransactions()); } /** @@ -503,6 +531,15 @@ import java.util.concurrent.atomic.AtomicInteger; */ /* package */ synchronized void onHubReset() { + if (Flags.reliableMessageRetrySupportService()) { + Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter = + mReliableMessageTransactionMap.entrySet().iterator(); + while (iter.hasNext()) { + completeMessageTransaction(iter.next().getValue(), + ContextHubTransaction.RESULT_FAILED_AT_HUB, iter); + } + } + ContextHubServiceTransaction transaction = mTransactionQueue.peek(); if (transaction == null) { return; @@ -566,7 +603,7 @@ import java.util.concurrent.atomic.AtomicInteger; long timeoutMs = transaction.getTimeout(TimeUnit.MILLISECONDS); try { - mTimeoutFuture = mTimeoutExecutor.schedule( + mTimeoutFuture = mExecutor.schedule( onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS); } catch (Exception e) { Log.e(TAG, "Error when schedule a timer", e); @@ -579,6 +616,138 @@ import java.util.concurrent.atomic.AtomicInteger; } } + /** + * Processes message transactions, starting and completing them as needed. + * <p> + * This function is called when adding a message transaction or when a timer + * expires for an existing message transaction's retry or timeout. The + * internal processing loop will iterate at most twice as if one iteration + * completes a transaction, the next iteration can only start new transactions. + * If the first iteration does not complete any transaction, the loop will + * only iterate once. + * <p> + */ + private synchronized void processMessageTransactions() { + if (!Flags.reliableMessageRetrySupportService()) { + return; + } + + if (mReliableMessageTransactionFuture != null) { + mReliableMessageTransactionFuture.cancel(/* mayInterruptIfRunning= */ false); + mReliableMessageTransactionFuture = null; + } + + long now = SystemClock.elapsedRealtimeNanos(); + long nextExecutionTime = Long.MAX_VALUE; + boolean continueProcessing; + do { + continueProcessing = false; + Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter = + mReliableMessageTransactionMap.entrySet().iterator(); + while (iter.hasNext()) { + ContextHubServiceTransaction transaction = iter.next().getValue(); + short hostEndpointId = transaction.getHostEndpointId(); + int numCompletedStartCalls = transaction.getNumCompletedStartCalls(); + if (numCompletedStartCalls == 0 + && mReliableMessageHostEndpointIdActiveSet.contains(hostEndpointId)) { + continue; + } + + long nextRetryTime = transaction.getNextRetryTime(); + long timeoutTime = transaction.getTimeoutTime(); + boolean transactionTimedOut = timeoutTime <= now; + boolean transactionHitMaxRetries = nextRetryTime <= now + && numCompletedStartCalls > RELIABLE_MESSAGE_MAX_NUM_RETRY; + if (transactionTimedOut || transactionHitMaxRetries) { + completeMessageTransaction(transaction, + ContextHubTransaction.RESULT_FAILED_TIMEOUT, iter); + continueProcessing = true; + } else { + if (nextRetryTime <= now || numCompletedStartCalls <= 0) { + startMessageTransaction(transaction, now); + } + + nextExecutionTime = Math.min(nextExecutionTime, + transaction.getNextRetryTime()); + nextExecutionTime = Math.min(nextExecutionTime, + transaction.getTimeoutTime()); + } + } + } while (continueProcessing); + + if (nextExecutionTime < Long.MAX_VALUE) { + mReliableMessageTransactionFuture = mExecutor.schedule( + () -> processMessageTransactions(), + Math.max(nextExecutionTime - SystemClock.elapsedRealtimeNanos(), + RELIABLE_MESSAGE_MIN_WAIT_TIME.toNanos()), + TimeUnit.NANOSECONDS); + } + } + + /** + * Completes a message transaction and removes it from the reliable message map. + * + * @param transaction The transaction to complete. + * @param result The result code. + */ + private void completeMessageTransaction(ContextHubServiceTransaction transaction, + @ContextHubTransaction.Result int result) { + completeMessageTransaction(transaction, result, /* iter= */ null); + } + + /** + * Completes a message transaction and removes it from the reliable message map using iter. + * + * @param transaction The transaction to complete. + * @param result The result code. + * @param iter The iterator for the reliable message map - used to remove the message directly. + */ + private void completeMessageTransaction(ContextHubServiceTransaction transaction, + @ContextHubTransaction.Result int result, + Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter) { + transaction.onTransactionComplete(result); + + if (iter == null) { + mReliableMessageTransactionMap.remove(transaction.getMessageSequenceNumber()); + } else { + iter.remove(); + } + mReliableMessageHostEndpointIdActiveSet.remove(transaction.getHostEndpointId()); + + Log.d(TAG, "Successfully completed reliable message transaction with " + + "message sequence number: " + transaction.getMessageSequenceNumber() + + " and result: " + result); + } + + /** + * Starts a message transaction. + * + * @param transaction The transaction to start. + * @param now The now time. + */ + private void startMessageTransaction(ContextHubServiceTransaction transaction, long now) { + int numCompletedStartCalls = transaction.getNumCompletedStartCalls(); + @ContextHubTransaction.Result int result = transaction.onTransact(); + if (result == ContextHubTransaction.RESULT_SUCCESS) { + Log.d(TAG, "Successfully " + + (numCompletedStartCalls == 0 ? "started" : "retried") + + " reliable message transaction with message sequence number: " + + transaction.getMessageSequenceNumber()); + } else { + Log.w(TAG, "Could not start reliable message transaction with " + + "message sequence number: " + + transaction.getMessageSequenceNumber() + + ", result: " + result); + } + + transaction.setNextRetryTime(now + RELIABLE_MESSAGE_RETRY_WAIT_TIME.toNanos()); + if (transaction.getTimeoutTime() == Long.MAX_VALUE) { // first time starting transaction + transaction.setTimeoutTime(now + RELIABLE_MESSAGE_TIMEOUT.toNanos()); + } + transaction.setNumCompletedStartCalls(numCompletedStartCalls + 1); + mReliableMessageHostEndpointIdActiveSet.add(transaction.getHostEndpointId()); + } + private int toStatsTransactionResult(@ContextHubTransaction.Result int result) { switch (result) { case ContextHubTransaction.RESULT_SUCCESS: @@ -605,19 +774,34 @@ import java.util.concurrent.atomic.AtomicInteger; @Override public String toString() { - StringBuilder sb = new StringBuilder(100); - ContextHubServiceTransaction[] arr; + StringBuilder sb = new StringBuilder(); + int i = 0; synchronized (this) { - arr = mTransactionQueue.toArray(new ContextHubServiceTransaction[0]); - } - for (int i = 0; i < arr.length; i++) { - sb.append(i + ": " + arr[i] + "\n"); - } + for (ContextHubServiceTransaction transaction: mTransactionQueue) { + sb.append(i); + sb.append(": "); + sb.append(transaction.toString()); + sb.append("\n"); + ++i; + } - sb.append("Transaction History:\n"); - Iterator<TransactionRecord> iterator = mTransactionRecordDeque.descendingIterator(); - while (iterator.hasNext()) { - sb.append(iterator.next() + "\n"); + if (Flags.reliableMessageRetrySupportService()) { + for (ContextHubServiceTransaction transaction: + mReliableMessageTransactionMap.values()) { + sb.append(i); + sb.append(": "); + sb.append(transaction.toString()); + sb.append("\n"); + ++i; + } + } + + sb.append("Transaction History:\n"); + Iterator<TransactionRecord> iterator = mTransactionRecordDeque.descendingIterator(); + while (iterator.hasNext()) { + sb.append(iterator.next()); + sb.append("\n"); + } } return sb.toString(); } diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index 552809bc7453..4fc3d8715a88 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -52,6 +52,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; /** * @hide @@ -432,10 +433,16 @@ public abstract class IContextHubWrapper { // Use this thread in case where the execution requires to be on a service thread. // For instance, AppOpsManager.noteOp requires the UPDATE_APP_OPS_STATS permission. - private HandlerThread mHandlerThread = + private final HandlerThread mHandlerThread = new HandlerThread("Context Hub AIDL callback", Process.THREAD_PRIORITY_BACKGROUND); private Handler mHandler; + // True if test mode is enabled for the Context Hub + private final AtomicBoolean mIsTestModeEnabled = new AtomicBoolean(false); + + // The test mode manager that manages behaviors during test mode + private final ContextHubTestModeManager mTestModeManager = new ContextHubTestModeManager(); + private class ContextHubAidlCallback extends android.hardware.contexthub.IContextHubCallback.Stub { private final int mContextHubId; @@ -549,6 +556,8 @@ public abstract class IContextHubWrapper { } else { Log.e(TAG, "mHandleServiceRestartCallback is not set"); } + + mIsTestModeEnabled.set(false); } public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException { @@ -659,7 +668,17 @@ public abstract class IContextHubWrapper { try { var msg = ContextHubServiceUtil.createAidlContextHubMessage( hostEndpointId, message); - hub.sendMessageToHub(contextHubId, msg); + + // Only process the message normally if not using test mode manager or if + // the test mode manager call returned false as this indicates it did not + // process the message. + boolean useTestModeManager = Flags.reliableMessageImplementation() + && Flags.reliableMessageTestModeBehavior() + && mIsTestModeEnabled.get(); + if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(message)) { + hub.sendMessageToHub(contextHubId, msg); + } + return ContextHubTransaction.RESULT_SUCCESS; } catch (RemoteException | ServiceSpecificException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; @@ -828,6 +847,7 @@ public abstract class IContextHubWrapper { return false; } + mIsTestModeEnabled.set(enable); try { hub.setTestMode(enable); return true; diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java index ec95298d0d13..58b14b14fdef 100644 --- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java +++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java @@ -28,6 +28,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.UserInfo; import android.media.AudioFocusInfo; import android.media.AudioManager; import android.media.AudioPlaybackConfiguration; @@ -183,8 +184,8 @@ public class BackgroundUserSoundNotifier { foregroundContext) throws RemoteException { final int userId = UserHandle.getUserId(afi.getClientUid()); final int usage = afi.getAttributes().getUsage(); - String userName = mUserManager.getUserInfo(userId).name; - if (userId != foregroundContext.getUserId()) { + UserInfo userInfo = mUserManager.getUserInfo(userId); + if (userInfo != null && userId != foregroundContext.getUserId()) { //TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE if (usage == USAGE_ALARM) { Intent muteIntent = createIntent(ACTION_MUTE_SOUND, afi, foregroundContext, userId); @@ -199,7 +200,7 @@ public class BackgroundUserSoundNotifier { mUserWithNotification = foregroundContext.getUserId(); mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(), - createNotification(userName, mutePI, switchPI, foregroundContext), + createNotification(userInfo.name, mutePI, switchPI, foregroundContext), foregroundContext.getUser()); } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 6eac72d84fd4..173fc5c86dd3 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2504,13 +2504,13 @@ final class InstallPackageHelper { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - private void enableRestrictedSettings(String pkgName, int appId, int userId) { + private void setAccessRestrictedSettingsMode(String pkgName, int appId, int userId, int mode) { final AppOpsManager appOpsManager = mPm.mContext.getSystemService(AppOpsManager.class); final int uid = UserHandle.getUid(userId, appId); appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, uid, pkgName, - AppOpsManager.MODE_ERRORED); + mode); } /** @@ -2888,8 +2888,21 @@ final class InstallPackageHelper { mPm.notifyPackageChanged(packageName, request.getAppId()); } - if (!android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() - || !android.security.Flags.extendEcmToAllSettings()) { + // Set the OP_ACCESS_RESTRICTED_SETTINGS op, which is used by ECM (see {@link + // EnhancedConfirmationManager}) as a persistent state denoting whether an app is + // currently guarded by ECM, not guarded by ECM, or (in Android V+) that this should + // be decided later. + if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() + && android.security.Flags.extendEcmToAllSettings()) { + final int appId = request.getAppId(); + mPm.mHandler.post(() -> { + for (int userId : firstUserIds) { + // MODE_DEFAULT means that the app's guardedness will be decided lazily + setAccessRestrictedSettingsMode(packageName, appId, userId, + AppOpsManager.MODE_DEFAULT); + } + }); + } else { // Apply restricted settings on potentially dangerous packages. Needs to happen // after appOpsManager is notified of the new package if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE @@ -2898,7 +2911,9 @@ final class InstallPackageHelper { final int appId = request.getAppId(); mPm.mHandler.post(() -> { for (int userId : firstUserIds) { - enableRestrictedSettings(packageName, appId, userId); + // MODE_ERRORED means that the app is explicitly guarded + setAccessRestrictedSettingsMode(packageName, appId, userId, + AppOpsManager.MODE_ERRORED); } }); } diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index eabc979c7b88..1b7bf89d7b44 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -28,6 +28,7 @@ import static com.android.server.power.stats.MobileRadioPowerStatsCollector.mapR import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.usage.NetworkStatsManager; @@ -1974,13 +1975,15 @@ public class BatteryStatsImpl extends BatteryStats { private WifiManager mWifiManager; private BluetoothPowerStatsCollector.BluetoothStatsRetriever mBluetoothStatsRetriever; + @SuppressLint("WifiManagerPotentialLeak") void setContext(Context context) { mPackageManager = context.getPackageManager(); mConsumedEnergyRetriever = new PowerStatsCollector.ConsumedEnergyRetrieverImpl( LocalServices.getService(PowerStatsInternal.class)); mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class); - mTelephonyManager = context.getSystemService(TelephonyManager.class); - mWifiManager = context.getSystemService(WifiManager.class); + mTelephonyManager = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mBluetoothStatsRetriever = new BluetoothStatsRetrieverImpl( context.getSystemService(BluetoothManager.class)); } @@ -11288,10 +11291,11 @@ public class BatteryStatsImpl extends BatteryStats { mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector( - mPowerStatsCollectorInjector); + mPowerStatsCollectorInjector, this::onMobileRadioPowerStatsRetrieved); mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats); - mWifiPowerStatsCollector = new WifiPowerStatsCollector(mPowerStatsCollectorInjector); + mWifiPowerStatsCollector = new WifiPowerStatsCollector(mPowerStatsCollectorInjector, + this::onWifiPowerStatsRetrieved); mWifiPowerStatsCollector.addConsumer(this::recordPowerStats); mBluetoothPowerStatsCollector = new BluetoothPowerStatsCollector( @@ -12320,16 +12324,13 @@ public class BatteryStatsImpl extends BatteryStats { /** * Distribute WiFi energy info and network traffic to apps. + * * @param info The energy information from the WiFi controller. */ @GuardedBy("this") public void updateWifiState(@Nullable final WifiActivityEnergyInfo info, final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs, @NonNull NetworkStatsManager networkStatsManager) { - if (mWifiPowerStatsCollector.isEnabled()) { - return; - } - if (DEBUG_ENERGY) { synchronized (mWifiNetworkLock) { Slog.d(TAG, "Updating wifi stats: " + Arrays.toString(mWifiIfaces)); @@ -12347,7 +12348,20 @@ public class BatteryStatsImpl extends BatteryStats { delta = null; } } + updateWifiBatteryStats(info, delta, consumedChargeUC, elapsedRealtimeMs, uptimeMs); + } + private void onWifiPowerStatsRetrieved(WifiActivityEnergyInfo wifiActivityEnergyInfo, + List<NetworkStatsDelta> networkStatsDeltas, long elapsedRealtimeMs, long uptimeMs) { + // Do not populate consumed energy, because energy attribution is done by + // WifiPowerStatsProcessor. + updateWifiBatteryStats(wifiActivityEnergyInfo, networkStatsDeltas, POWER_DATA_UNAVAILABLE, + elapsedRealtimeMs, uptimeMs); + } + + private void updateWifiBatteryStats(WifiActivityEnergyInfo info, + List<NetworkStatsDelta> delta, long consumedChargeUC, long elapsedRealtimeMs, + long uptimeMs) { synchronized (this) { if (!mOnBatteryInternal || mIgnoreNextExternalStats) { if (mIgnoreNextExternalStats) { @@ -12711,9 +12725,6 @@ public class BatteryStatsImpl extends BatteryStats { : mLastModemActivityInfo.getDelta(activityInfo); mLastModemActivityInfo = activityInfo; - // Add modem tx power to history. - addModemTxPowerToHistory(deltaInfo, elapsedRealtimeMs, uptimeMs); - // Grab a separate lock to acquire the network stats, which may do I/O. List<NetworkStatsDelta> delta = null; synchronized (mModemNetworkLock) { @@ -12724,6 +12735,23 @@ public class BatteryStatsImpl extends BatteryStats { } } + updateCellularBatteryStats(deltaInfo, delta, consumedChargeUC, elapsedRealtimeMs, uptimeMs); + } + + private void onMobileRadioPowerStatsRetrieved(ModemActivityInfo modemActivityInfo, + List<NetworkStatsDelta> networkStatsDeltas, long elapsedRealtimeMs, long uptimeMs) { + // Do not populate consumed energy, because energy attribution is done by + // MobileRadioPowerStatsProcessor. + updateCellularBatteryStats(modemActivityInfo, networkStatsDeltas, POWER_DATA_UNAVAILABLE, + elapsedRealtimeMs, uptimeMs); + } + + private void updateCellularBatteryStats(@Nullable ModemActivityInfo deltaInfo, + @Nullable List<NetworkStatsDelta> delta, long consumedChargeUC, long elapsedRealtimeMs, + long uptimeMs) { + // Add modem tx power to history. + addModemTxPowerToHistory(deltaInfo, elapsedRealtimeMs, uptimeMs); + synchronized (this) { final long totalRadioDurationMs = mMobileRadioActiveTimer.getTimeSinceMarkLocked( @@ -13111,7 +13139,6 @@ public class BatteryStatsImpl extends BatteryStats { final long rxTimeMs = deltaInfo.getReceiveTimeMillis(rat, freq); final int[] txTimesMs = deltaInfo.getTransmitTimeMillis(rat, freq); - ratStats.incrementRxDuration(freq, rxTimeMs); if (isMobileRadioEnergyConsumerSupportedLocked()) { // Accumulate the power cost of time spent receiving in a particular state. @@ -15387,16 +15414,15 @@ public class BatteryStatsImpl extends BatteryStats { /*@hide */ public WifiBatteryStats getWifiBatteryStats() { final int which = STATS_SINCE_CHARGED; - final long rawRealTimeUs = SystemClock.elapsedRealtime() * 1000; + final long rawRealTimeUs = mClock.elapsedRealtime() * 1000; final ControllerActivityCounter counter = getWifiControllerActivity(); final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which); final long scanTimeMs = counter.getScanTimeCounter().getCountLocked(which); final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which); final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which); - final long totalControllerActivityTimeMs - = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000; - final long sleepTimeMs - = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs); + final long totalControllerActivityTimeMs = + computeBatteryRealtime(mClock.elapsedRealtime() * 1000, which) / 1000; + final long sleepTimeMs = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs); final long energyConsumedMaMs = counter.getPowerCounter().getCountLocked(which); final long monitoredRailChargeConsumedMaMs = counter.getMonitoredRailChargeConsumedMaMs().getCountLocked(which); diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java index 33ea56399381..c88e1b0c0d1f 100644 --- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java @@ -16,6 +16,7 @@ package com.android.server.power.stats; +import android.annotation.Nullable; import android.content.pm.PackageManager; import android.hardware.power.stats.EnergyConsumerType; import android.net.NetworkStats; @@ -69,6 +70,13 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { AccessNetworkConstants.AccessNetworkType.NGRAN }; + interface Observer { + void onMobileRadioPowerStatsRetrieved( + @Nullable ModemActivityInfo modemActivityDelta, + @Nullable List<BatteryStatsImpl.NetworkStatsDelta> networkStatsDeltas, + long elapsedRealtimeMs, long uptimeMs); + } + interface Injector { Handler getHandler(); Clock getClock(); @@ -84,6 +92,7 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { } private final Injector mInjector; + private final Observer mObserver; private MobileRadioPowerStatsLayout mLayout; private boolean mIsInitialized; @@ -105,13 +114,14 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { private long mLastCallDuration; private long mLastScanDuration; - MobileRadioPowerStatsCollector(Injector injector) { + MobileRadioPowerStatsCollector(Injector injector, Observer observer) { super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod( BatteryConsumer.powerComponentIdToString( BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)), injector.getUidResolver(), injector.getClock()); mInjector = injector; + mObserver = observer; } @Override @@ -198,10 +208,8 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { Arrays.fill(mPowerStats.stats, 0); mPowerStats.uidStats.clear(); - collectModemActivityInfo(); - - collectNetworkStats(); - + ModemActivityInfo modemActivityDelta = collectModemActivityInfo(); + List<BatteryStatsImpl.NetworkStatsDelta> networkStatsDeltas = collectNetworkStats(); if (mEnergyConsumerIds.length != 0) { collectEnergyConsumers(); } @@ -210,12 +218,16 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { setTimestamp(mClock.elapsedRealtime()); } + if (mObserver != null) { + mObserver.onMobileRadioPowerStatsRetrieved(modemActivityDelta, + networkStatsDeltas, mClock.elapsedRealtime(), mClock.uptimeMillis()); + } return mPowerStats; } - private void collectModemActivityInfo() { + private ModemActivityInfo collectModemActivityInfo() { if (mTelephonyManager == null) { - return; + return null; } CompletableFuture<ModemActivityInfo> immediateFuture = new CompletableFuture<>(); @@ -243,7 +255,7 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { } if (activityInfo == null) { - return; + return null; } ModemActivityInfo deltaInfo = mLastModemActivityInfo == null @@ -293,12 +305,13 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { } } } + return deltaInfo; } - private void collectNetworkStats() { + private List<BatteryStatsImpl.NetworkStatsDelta> collectNetworkStats() { NetworkStats networkStats = mNetworkStatsSupplier.get(); if (networkStats == null) { - return; + return null; } List<BatteryStatsImpl.NetworkStatsDelta> delta = @@ -330,6 +343,7 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { mLayout.setUidTxPackets(stats, mLayout.getUidTxPackets(stats) + txPackets); } } + return delta; } private void collectEnergyConsumers() { diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java index 4bba649cb97f..549a97ea49cd 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java @@ -154,8 +154,11 @@ public class PowerStatsExporter { batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder( BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE); if (descriptor.powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { - deviceScope.addConsumedPowerForCustomComponent(descriptor.powerComponentId, - totalPower[0]); + if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent( + descriptor.powerComponentId)) { + deviceScope.addConsumedPowerForCustomComponent(descriptor.powerComponentId, + totalPower[0]); + } } else { deviceScope.addConsumedPower(descriptor.powerComponentId, totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED); diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java index bd04199fc227..6d519ee200c2 100644 --- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java @@ -43,6 +43,12 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { private static final long ENERGY_UNSPECIFIED = -1; + interface Observer { + void onWifiPowerStatsRetrieved(WifiActivityEnergyInfo info, + List<BatteryStatsImpl.NetworkStatsDelta> delta, long elapsedRealtimeMs, + long uptimeMs); + } + interface WifiStatsRetriever { interface Callback { void onWifiScanTime(int uid, long scanTimeMs, long batchScanTimeMs); @@ -66,6 +72,7 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { } private final Injector mInjector; + private final Observer mObserver; private WifiPowerStatsLayout mLayout; private boolean mIsInitialized; @@ -93,12 +100,13 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { private final SparseArray<WifiScanTimes> mLastScanTimes = new SparseArray<>(); private long mLastWifiActiveDuration; - WifiPowerStatsCollector(Injector injector) { + WifiPowerStatsCollector(Injector injector, Observer observer) { super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod( BatteryConsumer.powerComponentIdToString( BatteryConsumer.POWER_COMPONENT_WIFI)), injector.getUidResolver(), injector.getClock()); mInjector = injector; + mObserver = observer; } @Override @@ -160,22 +168,27 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { return null; } + WifiActivityEnergyInfo activityInfo = null; if (mPowerReportingSupported) { - collectWifiActivityInfo(); + activityInfo = collectWifiActivityInfo(); } else { collectWifiActivityStats(); } - collectNetworkStats(); + List<BatteryStatsImpl.NetworkStatsDelta> networkStatsDeltas = collectNetworkStats(); collectWifiScanTime(); if (mEnergyConsumerIds.length != 0) { collectEnergyConsumers(); } + if (mObserver != null) { + mObserver.onWifiPowerStatsRetrieved(activityInfo, networkStatsDeltas, + mClock.elapsedRealtime(), mClock.uptimeMillis()); + } return mPowerStats; } - private void collectWifiActivityInfo() { + private WifiActivityEnergyInfo collectWifiActivityInfo() { CompletableFuture<WifiActivityEnergyInfo> immediateFuture = new CompletableFuture<>(); mWifiManager.getWifiActivityEnergyInfoAsync(Runnable::run, immediateFuture::complete); @@ -190,7 +203,7 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { } if (activityInfo == null) { - return; + return null; } long rxDuration = activityInfo.getControllerRxDurationMillis() @@ -210,6 +223,9 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { mPowerStats.durationMs = rxDuration + txDuration + scanDuration + idleDuration; mLastWifiActivityInfo = activityInfo; + + return new WifiActivityEnergyInfo(activityInfo.getTimeSinceBootMillis(), + activityInfo.getStackState(), txDuration, rxDuration, scanDuration, idleDuration); } private void collectWifiActivityStats() { @@ -219,12 +235,12 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { mPowerStats.durationMs = duration; } - private void collectNetworkStats() { + private List<BatteryStatsImpl.NetworkStatsDelta> collectNetworkStats() { mPowerStats.uidStats.clear(); NetworkStats networkStats = mNetworkStatsSupplier.get(); if (networkStats == null) { - return; + return null; } List<BatteryStatsImpl.NetworkStatsDelta> delta = @@ -256,6 +272,7 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { mLayout.setUidTxPackets(stats, mLayout.getUidTxPackets(stats) + txPackets); } } + return delta; } private void collectWifiScanTime() { diff --git a/services/core/java/com/android/server/stats/pull/BatteryHealthUtility.java b/services/core/java/com/android/server/stats/pull/BatteryHealthUtility.java new file mode 100644 index 000000000000..e0768fe1f0e5 --- /dev/null +++ b/services/core/java/com/android/server/stats/pull/BatteryHealthUtility.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.stats.pull; + +import android.util.StatsEvent; + +import com.android.internal.util.FrameworkStatsLog; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + +/** + * Utility class to redact Battery Health data from HealthServiceWrapper + * + * @hide + */ +public abstract class BatteryHealthUtility { + /** + * Create a StatsEvent corresponding to the Battery Health data, the fields + * of which are redacted to preserve users' privacy. + * The redaction consists in truncating the timestamps to the Monday of the + * corresponding week, and reducing the battery serial into the last byte + * of its MD5. + */ + public static StatsEvent buildStatsEvent(int atomTag, + android.hardware.health.BatteryHealthData data, int chargeStatus, int chargePolicy) + throws NoSuchAlgorithmException { + int manufacturingDate = secondsToWeekYYYYMMDD(data.batteryManufacturingDateSeconds); + int firstUsageDate = secondsToWeekYYYYMMDD(data.batteryFirstUsageSeconds); + long stateOfHealth = data.batteryStateOfHealth; + int partStatus = data.batteryPartStatus; + int serialHashTruncated = stringToIntHash(data.batterySerialNumber) & 0xFF; // Last byte + + return FrameworkStatsLog.buildStatsEvent(atomTag, manufacturingDate, firstUsageDate, + (int) stateOfHealth, serialHashTruncated, partStatus, chargeStatus, chargePolicy); + } + + private static int secondsToWeekYYYYMMDD(long seconds) { + Calendar calendar = Calendar.getInstance(); + long millis = seconds * 1000L; + + calendar.setTimeInMillis(millis); + + // Truncate all date information, up to week, which is rounded to + // MONDAY + calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.US); + + String formattedDate = sdf.format(calendar.getTime()); + + return Integer.parseInt(formattedDate); + } + + private static int stringToIntHash(String data) throws NoSuchAlgorithmException { + if (data == null || data.isEmpty()) { + return 0; + } + + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] hashBytes = digest.digest(data.getBytes()); + + // Convert to integer (simplest way, but potential for loss of information) + BigInteger bigInt = new BigInteger(1, hashBytes); + return bigInt.intValue(); + } +} diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index c1b825b3f8d1..0041d39f4b2b 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -119,6 +119,8 @@ import android.net.NetworkStats; import android.net.NetworkTemplate; import android.net.wifi.WifiManager; import android.os.AsyncTask; +import android.os.BatteryManager; +import android.os.BatteryProperty; import android.os.BatteryStats; import android.os.BatteryStatsInternal; import android.os.BatteryStatsManager; @@ -243,6 +245,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.security.NoSuchAlgorithmException; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -769,6 +772,7 @@ public class StatsPullAtomService extends SystemService { case FrameworkStatsLog.FULL_BATTERY_CAPACITY: case FrameworkStatsLog.BATTERY_VOLTAGE: case FrameworkStatsLog.BATTERY_CYCLE_COUNT: + case FrameworkStatsLog.BATTERY_HEALTH: synchronized (mHealthHalLock) { return pullHealthHalLocked(atomTag, data); } @@ -999,6 +1003,7 @@ public class StatsPullAtomService extends SystemService { registerFullBatteryCapacity(); registerBatteryVoltage(); registerBatteryCycleCount(); + registerBatteryHealth(); registerSettingsStats(); registerInstalledIncrementalPackages(); registerKeystoreStorageStats(); @@ -4365,7 +4370,15 @@ public class StatsPullAtomService extends SystemService { ); } - int pullHealthHalLocked(int atomTag, List<StatsEvent> pulledData) { + private void registerBatteryHealth() { + int tagId = FrameworkStatsLog.BATTERY_HEALTH; + mStatsManager.setPullAtomCallback(tagId, + null, // use default PullAtomMetadata values + DIRECT_EXECUTOR, mStatsCallbackImpl); + } + + @GuardedBy("mHealthHalLock") + private int pullHealthHalLocked(int atomTag, List<StatsEvent> pulledData) { if (mHealthService == null) { return StatsManager.PULL_SKIP; } @@ -4396,6 +4409,44 @@ public class StatsPullAtomService extends SystemService { case FrameworkStatsLog.BATTERY_CYCLE_COUNT: pulledValue = healthInfo.batteryCycleCount; break; + case FrameworkStatsLog.BATTERY_HEALTH: + android.hardware.health.BatteryHealthData bhd; + try { + bhd = mHealthService.getBatteryHealthData(); + } catch (RemoteException | IllegalStateException e) { + return StatsManager.PULL_SKIP; + } + if (bhd == null) { + return StatsManager.PULL_SKIP; + } + + StatsEvent batteryHealthEvent; + try { + BatteryProperty chargeStatusProperty = new BatteryProperty(); + BatteryProperty chargePolicyProperty = new BatteryProperty(); + + if (0 > mHealthService.getProperty( + BatteryManager.BATTERY_PROPERTY_STATUS, chargeStatusProperty)) { + return StatsManager.PULL_SKIP; + } + if (0 > mHealthService.getProperty( + BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY, + chargePolicyProperty)) { + return StatsManager.PULL_SKIP; + } + int chargeStatus = (int) chargeStatusProperty.getLong(); + int chargePolicy = (int) chargePolicyProperty.getLong(); + batteryHealthEvent = BatteryHealthUtility.buildStatsEvent( + atomTag, bhd, chargeStatus, chargePolicy); + pulledData.add(batteryHealthEvent); + + return StatsManager.PULL_SUCCESS; + } catch (RemoteException | IllegalStateException e) { + Slog.e(TAG, "Failed to add pulled data", e); + } catch (NoSuchAlgorithmException e) { + Slog.e(TAG, "Could not find message digest algorithm", e); + } + return StatsManager.PULL_SKIP; default: return StatsManager.PULL_SKIP; } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index b8469474aa18..72c7be34597e 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -101,6 +101,7 @@ import android.os.ShellCallback; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.service.wallpaper.IWallpaperConnection; import android.service.wallpaper.IWallpaperEngine; import android.service.wallpaper.IWallpaperService; @@ -2209,8 +2210,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId, IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId, boolean getCropped) { - final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL); - if (!hasPrivilege) checkPermission(MANAGE_EXTERNAL_STORAGE); + final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL) + || hasPermission(MANAGE_EXTERNAL_STORAGE); + if (!hasPrivilege) { + mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true, + Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId); + } wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), wallpaperUserId, false, true, "getWallpaper", null); diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 3e177c9fe8c6..a8dcaa8a90e1 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -102,7 +102,7 @@ import android.window.TransitionInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AssistUtils; import com.android.internal.policy.IKeyguardDismissCallback; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.LocalServices; import com.android.server.Watchdog; import com.android.server.pm.KnownPackages; diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 6b25c84daf48..7d7592fa0838 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -386,7 +386,7 @@ import com.android.internal.content.ReferrerIntent; import com.android.internal.os.TimeoutRecord; import com.android.internal.os.TransferPipe; import com.android.internal.policy.AttributeCache; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -4578,9 +4578,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (!delayed) { updateReportedVisibilityLocked(); } - - // Reset the last saved PiP snap fraction on removal. - mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent); mDisplayContent.onRunningActivityChanged(); mRemovingFromDisplay = false; } @@ -6744,8 +6741,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (task.mLastRecentsAnimationTransaction != null) { task.clearLastRecentsAnimationTransaction(true /* forceRemoveOverlay */); } - // Reset the last saved PiP snap fraction on app stop. - mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent); if (isClientVisible()) { // Though this is usually unlikely to happen, still make sure the client is invisible. setClientVisible(false); diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java index 23a97089fd60..056c09eb0ce9 100644 --- a/services/core/java/com/android/server/wm/ActivityRefresher.java +++ b/services/core/java/com/android/server/wm/ActivityRefresher.java @@ -27,7 +27,7 @@ import android.content.res.Configuration; import android.os.Handler; import android.os.RemoteException; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 717c3998aba4..5bfe9d744975 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -127,7 +127,7 @@ import android.window.RemoteTransition; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.app.IVoiceInteractor; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.am.PendingIntentRecord; import com.android.server.pm.InstantAppResolver; import com.android.server.pm.PackageArchiver; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 2109f5d5ab8f..dded1ca93270 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -260,7 +260,7 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.TransferPipe; import com.android.internal.policy.AttributeCache; import com.android.internal.policy.KeyguardDismissCallback; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FrameworkStatsLog; diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index cd5576fca2f9..d65a106a1079 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -148,7 +148,7 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java index 0013c5c63798..80671effdc98 100644 --- a/services/core/java/com/android/server/wm/AnrController.java +++ b/services/core/java/com/android/server/wm/AnrController.java @@ -345,7 +345,7 @@ class AnrController { null /* processCpuTracker */, null /* lastPids */, CompletableFuture.completedFuture(nativePids), null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents, - Runnable::run, null/* AnrLatencyTracker */); + null /* extraHeaders */, Runnable::run, null/* AnrLatencyTracker */); if (tracesFile != null) { tracesFile.renameTo( new File(tracesFile.getParent(), tracesFile.getName() + "_pre")); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index c2b9128daac5..bc7e84ae9f86 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -135,7 +135,7 @@ import android.view.animation.TranslateAnimation; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.common.LogLevel; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.DumpUtils.Dump; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index c55a1003f402..44b414f1ea7e 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -92,7 +92,7 @@ import android.view.WindowManager.TransitionType; import android.window.ITaskFragmentOrganizer; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index a8cc2ae161cf..4554b210e3fd 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -30,7 +30,7 @@ import android.util.Slog; import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 14ae918129f7..cb690394480e 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -61,7 +61,7 @@ import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.TransitionAnimation; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java index 0b2c851c4366..ba4ab7df8270 100644 --- a/services/core/java/com/android/server/wm/BlackFrame.java +++ b/services/core/java/com/android/server/wm/BlackFrame.java @@ -22,7 +22,7 @@ import android.graphics.Rect; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.io.PrintWriter; import java.util.function.Supplier; diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java index 0c751cfe4f46..92040177ad9e 100644 --- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java +++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java @@ -32,7 +32,7 @@ import android.content.res.Configuration; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; /** diff --git a/services/core/java/com/android/server/wm/CameraStateMonitor.java b/services/core/java/com/android/server/wm/CameraStateMonitor.java index ea7edea7d4b3..a54141ce5230 100644 --- a/services/core/java/com/android/server/wm/CameraStateMonitor.java +++ b/services/core/java/com/android/server/wm/CameraStateMonitor.java @@ -26,7 +26,7 @@ import android.os.Handler; import android.util.ArraySet; import android.util.Slog; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.util.ArrayList; import java.util.List; diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java index b795987f1d11..44202a2ae58e 100644 --- a/services/core/java/com/android/server/wm/CompatModePackages.java +++ b/services/core/java/com/android/server/wm/CompatModePackages.java @@ -48,7 +48,7 @@ import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.Xml; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index d70a88062ebd..7e7073c4a52c 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -42,7 +42,7 @@ import android.view.DisplayInfo; import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.display.feature.DisplayManagerFlags; /** diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java index b5890856fa6f..283f819fc6ae 100644 --- a/services/core/java/com/android/server/wm/ContentRecordingController.java +++ b/services/core/java/com/android/server/wm/ContentRecordingController.java @@ -23,7 +23,7 @@ import android.annotation.Nullable; import android.view.ContentRecordingSession; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; /** * Orchestrates the handoff between displays if the recording session changes, and keeps track of diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java index be44629a1fcf..3b999549b302 100644 --- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -36,7 +36,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater; import com.android.window.flags.Flags; diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java index 735c73a990cb..22fa88f12386 100644 --- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java +++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java @@ -29,7 +29,7 @@ import android.util.Log; import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 0006bd250087..def495f3daa5 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -45,7 +45,7 @@ import android.window.DisplayAreaInfo; import android.window.IDisplayAreaOrganizer; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java index 3dc3be9abf74..8f471d797904 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java +++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java @@ -33,7 +33,7 @@ import android.window.IDisplayAreaOrganizer; import android.window.IDisplayAreaOrganizerController; import android.window.WindowContainerToken; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.util.ArrayList; import java.util.HashMap; diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java index 8c52288c4be5..bb596cc829c9 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -42,7 +42,7 @@ import android.window.WindowContainerToken; import com.android.internal.annotations.Keep; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.policy.WindowManagerPolicy; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 7ffffe97b646..b5b937775333 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -254,7 +254,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ToBooleanFunction; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 4a59fc2a8f15..b36fbd34866c 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -132,7 +132,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.policy.ForceShowNavBarSettingsObserver; import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.statusbar.LetterboxDetails; import com.android.internal.util.ScreenshotHelper; import com.android.internal.util.ScreenshotRequest; diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index afcf364d4729..f3ccc3b5aef8 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -80,7 +80,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.LocalServices; import com.android.server.UiThread; import com.android.server.policy.WindowManagerPolicy; diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index 6ecafdb03d20..beb376788367 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -42,7 +42,7 @@ import android.widget.Toast; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.UiThread; /** diff --git a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java index 50b29ec965ea..f94b8c41b24a 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java +++ b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java @@ -26,7 +26,7 @@ import android.annotation.Nullable; import android.content.ActivityInfoProto; import android.view.Surface; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; /** * Defines the behavior of reversion from device rotation overrides. diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java index c79565ae79fa..8bd8098b6be9 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java +++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.os.UserHandle.USER_SYSTEM; import static android.view.Display.TYPE_VIRTUAL; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; @@ -27,6 +28,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.WindowConfiguration; import android.os.Environment; import android.util.ArrayMap; @@ -42,6 +44,7 @@ import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.wm.DisplayWindowSettings.SettingsProvider; +import com.android.window.flags.Flags; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -53,6 +56,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; +import java.util.Set; /** * Implementation of {@link SettingsProvider} that reads the base settings provided in a display @@ -91,11 +95,11 @@ class DisplayWindowSettingsProvider implements SettingsProvider { @NonNull private ReadableSettings mBaseSettings; @NonNull - private final WritableSettings mOverrideSettings; + private WritableSettings mOverrideSettings; DisplayWindowSettingsProvider() { this(new AtomicFileStorage(getVendorSettingsFile()), - new AtomicFileStorage(getOverrideSettingsFile())); + new AtomicFileStorage(getOverrideSettingsFileForUser(USER_SYSTEM))); } @VisibleForTesting @@ -133,6 +137,48 @@ class DisplayWindowSettingsProvider implements SettingsProvider { mBaseSettings = new ReadableSettings(baseSettingsStorage); } + /** + * Overrides the storage that should be used to save override settings for a user. + * + * @see #DATA_DISPLAY_SETTINGS_FILE_PATH + */ + void setOverrideSettingsForUser(@UserIdInt int userId) { + if (!Flags.perUserDisplayWindowSettings()) { + return; + } + final AtomicFile settingsFile = getOverrideSettingsFileForUser(userId); + setOverrideSettingsStorage(new AtomicFileStorage(settingsFile)); + } + + /** + * Removes display override settings that are no longer associated with active displays. + * This is necessary because displays can be dynamically added or removed during + * the system's lifecycle (e.g., user switch, system server restart). + * + * @param root The root window container used to obtain the currently active displays. + */ + void removeStaleDisplaySettings(@NonNull RootWindowContainer root) { + if (!Flags.perUserDisplayWindowSettings()) { + return; + } + final Set<String> displayIdentifiers = new ArraySet<>(); + root.forAllDisplays(dc -> { + final String identifier = mOverrideSettings.getIdentifier(dc.getDisplayInfo()); + displayIdentifiers.add(identifier); + }); + mOverrideSettings.removeStaleDisplaySettings(displayIdentifiers); + } + + /** + * Overrides the storage that should be used to save override settings. + * + * @see #setOverrideSettingsForUser(int) + */ + @VisibleForTesting + void setOverrideSettingsStorage(@NonNull WritableSettingsStorage overrideSettingsStorage) { + mOverrideSettings = new WritableSettings(overrideSettingsStorage); + } + @Override @NonNull public SettingsEntry getSettings(@NonNull DisplayInfo info) { @@ -302,6 +348,12 @@ class DisplayWindowSettingsProvider implements SettingsProvider { mVirtualDisplayIdentifiers.remove(identifier); } + void removeStaleDisplaySettings(@NonNull Set<String> currentDisplayIdentifiers) { + if (mSettings.retainAll(currentDisplayIdentifiers)) { + writeSettings(); + } + } + private void writeSettings() { final FileData fileData = new FileData(); fileData.mIdentifierType = mIdentifierType; @@ -332,9 +384,14 @@ class DisplayWindowSettingsProvider implements SettingsProvider { } @NonNull - private static AtomicFile getOverrideSettingsFile() { - final File overrideSettingsFile = new File(Environment.getDataDirectory(), - DATA_DISPLAY_SETTINGS_FILE_PATH); + private static AtomicFile getOverrideSettingsFileForUser(@UserIdInt int userId) { + final File directory; + if (userId == USER_SYSTEM || !Flags.perUserDisplayWindowSettings()) { + directory = Environment.getDataDirectory(); + } else { + directory = Environment.getDataSystemCeDirectory(userId); + } + final File overrideSettingsFile = new File(directory, DATA_DISPLAY_SETTINGS_FILE_PATH); return new AtomicFile(overrideSettingsFile, WM_DISPLAY_COMMIT_TAG); } diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index e3827aa86d9e..4be5bad644f7 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -64,7 +64,7 @@ import android.view.WindowManager; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.view.IDragAndDropPermissions; import com.android.server.LocalServices; import com.android.server.pm.UserManagerInternal; diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index a21ba2603ec6..c66d6596226a 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -35,7 +35,7 @@ import android.view.InputApplicationHandle; import android.view.InputChannel; import android.window.InputTransferToken; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.input.InputManagerService; /** diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 156e9f98b7a7..91c61b1bd550 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -40,7 +40,7 @@ import android.view.inputmethod.Flags; import android.view.inputmethod.ImeTracker; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 8035a298e45a..74dbd15d1399 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -66,7 +66,7 @@ import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.SoftInputShowHideReason; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.LocalServices; import com.android.server.inputmethod.InputMethodManagerInternal; diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index f68b67f626f9..33dea54574e6 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -48,7 +48,7 @@ import android.view.SurfaceControl.Transaction; import android.view.WindowInsets; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.function.TriFunction; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index a967f7af3bb9..348384203fba 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -41,7 +41,7 @@ import android.view.InsetsState; import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.inputmethod.InputMethodManagerInternal; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index 0f9998cafc4e..0dadade38ddb 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -63,7 +63,7 @@ import android.util.SparseIntArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.IKeyguardDismissCallback; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.telephony.CellBroadcastUtils; import com.android.internal.widget.LockPatternUtils; diff --git a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java index 36c092b3f535..1a895ea22f36 100644 --- a/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/NonAppWindowAnimationAdapter.java @@ -35,7 +35,7 @@ import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.view.WindowManager; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java index 4c797f8d17ea..3cf301c1d730 100644 --- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java +++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java @@ -36,7 +36,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.wm.DeviceStateController.DeviceState; public class PhysicalDisplaySwitchTransitionLauncher { diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java index 4378b4f17b5b..755d4c8c9fc5 100644 --- a/services/core/java/com/android/server/wm/PinnedTaskController.java +++ b/services/core/java/com/android/server/wm/PinnedTaskController.java @@ -22,7 +22,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.app.PictureInPictureParams; -import android.content.ComponentName; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Matrix; @@ -326,19 +325,6 @@ class PinnedTaskController { } /** - * Activity is hidden (either stopped or removed), resets the last saved snap fraction - * so that the default bounds will be returned for the next session. - */ - void onActivityHidden(ComponentName componentName) { - if (mPinnedTaskListener == null) return; - try { - mPinnedTaskListener.onActivityHidden(componentName); - } catch (RemoteException e) { - Slog.e(TAG_WM, "Error delivering reset reentry fraction event.", e); - } - } - - /** * Sets the Ime state and height. */ void setAdjustedForIme(boolean adjustedForIme, int imeHeight) { diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index e07b72a05123..6f2528085d74 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -80,7 +80,7 @@ import android.view.WindowInsets; import android.view.WindowManagerPolicyConstants.PointerEventListener; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.am.ActivityManagerService; diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index 469cc647b1b5..c592caf488ad 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -42,7 +42,7 @@ import android.os.Trace; import android.util.Slog; import android.view.IRecentsAnimationRunner; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 312c4befb0d6..6f947135b789 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -64,7 +64,7 @@ import android.window.WindowAnimationState; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.LocalServices; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.statusbar.StatusBarManagerInternal; diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 63bbb3140bfb..a8edaebd24a6 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -45,7 +45,7 @@ import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.LogLevel; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.FastPrintWriter; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; diff --git a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java index c22b07a4efa1..e4962bffc570 100644 --- a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java +++ b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java @@ -28,7 +28,7 @@ import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.util.ArrayList; import java.util.List; diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java index d497d8cbf9cd..243dbc78998d 100644 --- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java +++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java @@ -24,7 +24,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.os.Debug; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.util.ArrayList; import java.util.function.Consumer; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 6abd4887645b..d1f1cab43b8a 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -141,7 +141,7 @@ import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ResolverActivity; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; import com.android.server.LocalServices; @@ -2158,6 +2158,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Use Task#setBoundsUnchecked to skip checking windowing mode as the windowing mode // will be updated later after this is collected in transition. rootTask.setBoundsUnchecked(taskFragment.getBounds()); + // The exit-PIP activity resumes early for seamless transition. In certain + // scenarios, this introduces unintended addition to recents. To address this, + // we mark the root task for automatic removal from recents. This ensures that + // after the pinned activity reparents to its original task, the root task is + // automatically removed from the recents list. + rootTask.autoRemoveRecents = true; // Move the last recents animation transaction from original task to the new one. if (task.mLastRecentsAnimationTransaction != null) { diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java index 967f415d2c11..ad4faab1e106 100644 --- a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java +++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java @@ -33,7 +33,7 @@ import android.view.ContentRecordingSession; import android.window.IScreenRecordingCallback; import com.android.internal.annotations.GuardedBy; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.io.PrintWriter; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index e7bffdfd448b..3eb321804520 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -57,7 +57,7 @@ import android.window.ScreenCapture; import com.android.internal.R; import com.android.internal.policy.TransitionAnimation; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.display.DisplayControl; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 4a0239bc29b2..9addce6d5f08 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -84,7 +84,7 @@ import android.window.OnBackInvokedCallbackInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.logging.MetricsLoggerWrapper; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.LocalServices; import com.android.server.wm.WindowManagerService.H; import com.android.window.flags.Flags; diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java index b5d94a2efbdf..2b4d901e8231 100644 --- a/services/core/java/com/android/server/wm/SmoothDimmer.java +++ b/services/core/java/com/android/server/wm/SmoothDimmer.java @@ -26,7 +26,7 @@ import android.view.Surface; import android.view.SurfaceControl; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; class SmoothDimmer extends Dimmer { diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index c632714bd8ce..9cfd39688c12 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -33,7 +33,7 @@ import android.view.SurfaceControl.Transaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.LogLevel; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java index 0c36d27603c8..34abf23daa2a 100644 --- a/services/core/java/com/android/server/wm/SurfaceFreezer.java +++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java @@ -31,7 +31,7 @@ import android.view.SurfaceControl; import android.window.ScreenCapture; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; /** * This class handles "freezing" of an Animatable. The Animatable in question should implement diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index f9c53aa35bd8..52a1ed98a4ce 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -181,7 +181,7 @@ import android.window.WindowContainerToken; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index eff831552320..eaf3012a3b11 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -52,7 +52,7 @@ import android.view.SurfaceControl; import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.util.function.pooled.PooledPredicate; diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 3cd071bd329e..9b2c022df963 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -106,7 +106,7 @@ import android.window.TaskFragmentInfo; import android.window.TaskFragmentOrganizerToken; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ToBooleanFunction; import com.android.server.am.HostingRecord; import com.android.server.pm.pkg.AndroidPackage; diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 26315f9cc2c3..b6b6cf2dc430 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -59,7 +59,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import java.lang.annotation.Retention; diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index b24d53b26caa..6e36d427bd13 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -57,7 +57,7 @@ import android.window.TaskSnapshot; import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java index 9b3fb6b881c4..972dd2e382cc 100644 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ b/services/core/java/com/android/server/wm/TaskPositioner.java @@ -59,7 +59,7 @@ import android.view.WindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.TaskResizingAlgorithm; import com.android.internal.policy.TaskResizingAlgorithm.CtrlType; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.util.concurrent.CompletableFuture; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index e698a3d95841..35a77022737f 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -106,7 +106,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.internal.policy.TransitionAnimation; import com.android.internal.protolog.ProtoLogGroup; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.statusbar.StatusBarManagerInternal; diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 08123232866b..f4ff404c2bff 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -53,7 +53,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.FgThread; import com.android.window.flags.Flags; diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java index fa2d9bf21dee..c0dc4247a7c2 100644 --- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java +++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java @@ -40,7 +40,7 @@ import android.window.ITrustedPresentationListener; import android.window.TrustedPresentationThresholds; import android.window.WindowInfosListener; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.wm.utils.RegionUtils; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java index 4a5a20e57804..fffe692a39dd 100644 --- a/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java +++ b/services/core/java/com/android/server/wm/WallpaperAnimationAdapter.java @@ -27,7 +27,7 @@ import android.util.proto.ProtoOutputStream; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.wm.SurfaceAnimator.AnimationType; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 43f7ecc6ab1c..3b5a6058e803 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -57,7 +57,7 @@ import android.window.ScreenCapture; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ToBooleanFunction; import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; import com.android.window.flags.Flags; diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index b7f8505b4a65..31156de5debf 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -31,7 +31,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.SparseArray; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import java.util.function.Consumer; diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 8afcf0e1e05a..03342d316d1c 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -34,7 +34,7 @@ import android.util.TimeUtils; import android.view.Choreographer; import android.view.SurfaceControl; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 1f31af68c693..325ef0d184df 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -111,7 +111,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.internal.protolog.common.LogLevel; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ToBooleanFunction; import com.android.server.wm.SurfaceAnimator.Animatable; import com.android.server.wm.SurfaceAnimator.AnimationType; diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java index b000a9841d06..57fc4c7a6860 100644 --- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java +++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java @@ -39,7 +39,7 @@ import android.view.SurfaceControl.Builder; import android.view.SurfaceControl.Transaction; import android.view.animation.Animation; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.wm.SurfaceAnimator.Animatable; import com.android.server.wm.SurfaceAnimator.AnimationType; diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java index 21f251fbc736..cd785e5174e5 100644 --- a/services/core/java/com/android/server/wm/WindowContextListenerController.java +++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java @@ -39,7 +39,7 @@ import android.view.WindowManager.LayoutParams.WindowType; import android.window.WindowContext; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.util.Objects; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 72ec05847973..57b8040ef0ac 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -335,7 +335,7 @@ import com.android.internal.policy.IShortcutService; import com.android.internal.policy.KeyInterceptionInfo; import com.android.internal.protolog.LegacyProtoLogImpl; import com.android.internal.protolog.ProtoLogGroup; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FrameworkStatsLog; @@ -3743,6 +3743,8 @@ public class WindowManagerService extends IWindowManager.Stub null /* trigger */, null /* remote */, null /* disp */); } mCurrentUserId = newUserId; + mDisplayWindowSettingsProvider.setOverrideSettingsForUser(newUserId); + mDisplayWindowSettingsProvider.removeStaleDisplaySettings(mRoot); mPolicy.setCurrentUserLw(newUserId); mKeyguardDisableHandler.setCurrentUser(newUserId); @@ -3796,7 +3798,7 @@ public class WindowManagerService extends IWindowManager.Stub hideBootMessagesLocked(); // If the screen still doesn't come up after 30 seconds, give // up and turn it on. - mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30 * 1000); + mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30 * 1000 * Build.HW_TIMEOUT_MULTIPLIER); } mPolicy.systemBooted(); @@ -5479,6 +5481,9 @@ public class WindowManagerService extends IWindowManager.Stub // DisplayWindowSettings are applied. In addition, wide-color/hdr/isTouchDevice also // affect the Configuration. mRoot.forAllDisplays(DisplayContent::reconfigureDisplayLocked); + // Per-user display settings may leave outdated settings after user switches, especially + // during reboots starting with the default user without setCurrentUser called. + mDisplayWindowSettingsProvider.removeStaleDisplaySettings(mRoot); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 1f06bfac5668..6febe80166f4 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -51,7 +51,7 @@ import com.android.internal.os.ByteTransferPipe; import com.android.internal.protolog.LegacyProtoLogImpl; import com.android.internal.protolog.PerfettoProtoLogImpl; import com.android.internal.protolog.common.IProtoLog; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.IoThread; import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; import com.android.server.wm.LetterboxConfiguration.LetterboxHorizontalReachabilityPosition; diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index d26df7a67ad5..de584573fcaf 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -124,7 +124,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLogGroup; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal; @@ -1105,6 +1105,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub break; } if (activity.isVisible() || activity.isVisibleRequested()) { + effects |= TRANSACT_EFFECTS_LIFECYCLE; // Prevent the transition from being executed too early if the activity is // visible. activity.finishIfPossible("finish-activity-op", false /* oomAdj */); @@ -1122,6 +1123,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID); final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid); + effects |= TRANSACT_EFFECTS_LIFECYCLE; waitAsyncStart(() -> mService.mTaskSupervisor.startActivityFromRecents( caller.mPid, caller.mUid, taskId, safeOptions)); break; diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index b8780726f992..60d3e787cac4 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -82,7 +82,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.HeavyWeightSwitcherActivity; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.Watchdog; import com.android.server.grammaticalinflection.GrammaticalInflectionManagerInternal; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index d845968767e5..fec1175785ea 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -250,7 +250,7 @@ import android.window.OnBackInvokedCallbackInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.KeyInterceptionInfo; import com.android.internal.protolog.common.LogLevel; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.ToBooleanFunction; import com.android.server.policy.WindowManagerPolicy; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 9ecd49213237..397a6357fb66 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -62,7 +62,7 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import com.android.internal.protolog.common.LogLevel; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; import com.android.server.policy.WindowManagerPolicy; diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index 4456a94ef510..d9766e0dfa61 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -36,7 +36,7 @@ import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl; import android.view.WindowContentFrameStats; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 4dca23bc03c7..11ef2cde65a9 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -47,7 +47,7 @@ import android.view.WindowManager; import android.view.WindowManager.LayoutParams.WindowType; import android.window.WindowContext; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index b0e71bda787a..ba5323ee1f8f 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -37,7 +37,7 @@ import android.view.Choreographer; import com.android.internal.protolog.LegacyProtoLogImpl; import com.android.internal.protolog.common.IProtoLog; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.TraceBuffer; import java.io.File; diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java index d4adba209cd9..33ea9b459b31 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -50,6 +50,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.inputmethod.InputBindResult; +import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; @@ -73,9 +74,8 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe @Before public void setUp() throws RemoteException { super.setUp(); - mVisibilityApplier = - (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier(); synchronized (ImfLock.class) { + mVisibilityApplier = mInputMethodManagerService.getVisibilityApplierLocked(); mUserId = mInputMethodManagerService.getCurrentImeUserIdLocked(); mInputMethodManagerService.setAttachedClientForTesting(requireNonNull( mInputMethodManagerService.getClientStateLocked(mMockInputMethodClient))); @@ -106,7 +106,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe assertThrows(IllegalArgumentException.class, () -> { synchronized (ImfLock.class) { mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), - STATE_INVALID, mUserId); + STATE_INVALID, eq(SoftInputShowHideReason.NOT_SET), mUserId); } }); } @@ -116,7 +116,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe final var statsToken = ImeTracker.Token.empty(); synchronized (ImfLock.class) { mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_SHOW_IME, - mUserId); + eq(SoftInputShowHideReason.NOT_SET), mUserId); } verify(mMockWindowManagerInternal).showImePostLayout(eq(mWindowToken), eq(statsToken)); } @@ -126,7 +126,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe final var statsToken = ImeTracker.Token.empty(); synchronized (ImfLock.class) { mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_HIDE_IME, - mUserId); + eq(SoftInputShowHideReason.NOT_SET), mUserId); } verify(mMockWindowManagerInternal).hideIme(eq(mWindowToken), anyInt() /* displayId */, eq(statsToken)); @@ -137,7 +137,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe mInputMethodManagerService.mImeWindowVis = IME_ACTIVE; synchronized (ImfLock.class) { mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), - STATE_HIDE_IME_EXPLICIT, mUserId); + STATE_HIDE_IME_EXPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId); } verifyHideSoftInput(true, true); } @@ -147,7 +147,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe mInputMethodManagerService.mImeWindowVis = IME_ACTIVE; synchronized (ImfLock.class) { mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), - STATE_HIDE_IME_NOT_ALWAYS, mUserId); + STATE_HIDE_IME_NOT_ALWAYS, eq(SoftInputShowHideReason.NOT_SET), mUserId); } verifyHideSoftInput(true, true); } @@ -156,7 +156,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe public void testApplyImeVisibility_showImeImplicit() throws Exception { synchronized (ImfLock.class) { mVisibilityApplier.applyImeVisibility(mWindowToken, ImeTracker.Token.empty(), - STATE_SHOW_IME_IMPLICIT, mUserId); + STATE_SHOW_IME_IMPLICIT, eq(SoftInputShowHideReason.NOT_SET), mUserId); } verifyShowSoftInput(true, true, 0 /* showFlags */); } @@ -177,7 +177,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe // Verify hideIme will apply the expected displayId when the default IME // visibility applier app STATE_HIDE_IME. mVisibilityApplier.applyImeVisibility(mWindowToken, statsToken, STATE_HIDE_IME, - mUserId); + eq(SoftInputShowHideReason.NOT_SET), mUserId); verify(mInputMethodManagerService.mWindowManagerInternal).hideIme( eq(mWindowToken), eq(displayIdToShowIme), eq(statsToken)); } @@ -217,14 +217,15 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe final int displayIdToShowIme = bindingController.getDisplayIdToShowIme(); mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, statsToken, 0 /* flags */, null /* resultReceiver */, - HIDE_SWITCH_USER); - mInputMethodManagerService.onUnbindCurrentMethodByReset(); + HIDE_SWITCH_USER, mUserId); + mInputMethodManagerService.onUnbindCurrentMethodByReset(mUserId); // Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing // the IME hidden state. // The unbind will cancel the previous stats token, and create a new one internally. verify(mVisibilityApplier).applyImeVisibility( - eq(mWindowToken), any(), eq(STATE_HIDE_IME), eq(mUserId) /* userId */); + eq(mWindowToken), any(), eq(STATE_HIDE_IME), + eq(SoftInputShowHideReason.NOT_SET), eq(mUserId) /* userId */); verify(mInputMethodManagerService.mWindowManagerInternal).hideIme( eq(mWindowToken), eq(displayIdToShowIme), and(not(eq(statsToken)), notNull())); } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java index a22cacbcb5df..337d5c1faf94 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java @@ -58,8 +58,8 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; /** - * Test the behavior of {@link ImeVisibilityStateComputer} and {@link ImeVisibilityApplier} when - * requesting the IME visibility. + * Test the behavior of {@link ImeVisibilityStateComputer} and {@link DefaultImeVisibilityApplier} + * when requesting the IME visibility. * * <p> Build/Install/Run: * atest FrameworksInputMethodSystemServerTests:ImeVisibilityStateComputerTest diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index 42bd75a7a67e..80eab112d814 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -189,6 +189,7 @@ public class InputMethodManagerServiceTestBase { // Injecting and mocked InputMethodBindingController and InputMethod. mMockInputMethodInvoker = IInputMethodInvoker.create(mMockInputMethod); mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mMockIInputManager); + when(mMockInputMethodBindingController.getUserId()).thenReturn(mCallingUserId); synchronized (ImfLock.class) { when(mMockInputMethodBindingController.getCurMethod()) .thenReturn(mMockInputMethodInvoker); diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java index f9f45057f57f..e2f3eec1a20b 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java @@ -106,7 +106,7 @@ public final class UserDataRepositoryTest { // Assert UserDataRepository called the InputMethodBindingController creator function. verify(bindingControllerFactorySpy).apply(ANY_USER_ID); - assertThat(allUserData.get(0).mBindingController.mUserId).isEqualTo(ANY_USER_ID); + assertThat(allUserData.get(0).mBindingController.getUserId()).isEqualTo(ANY_USER_ID); } @Test @@ -149,7 +149,7 @@ public final class UserDataRepositoryTest { assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID); // Assert UserDataRepository called the InputMethodBindingController creator function. - assertThat(allUserData.get(0).mBindingController.mUserId).isEqualTo(ANY_USER_ID); + assertThat(allUserData.get(0).mBindingController.getUserId()).isEqualTo(ANY_USER_ID); } private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index bb774eec9d4e..7b8b712c1aee 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -1707,7 +1707,8 @@ public final class DisplayPowerControllerTest { int initState = Display.STATE_OFF; mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); - when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true); + ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class); + when(mDisplayOffloadSession.blockScreenOn(argumentCaptor.capture())).thenReturn(true); // Start with OFF. when(mHolder.displayPowerState.getScreenState()).thenReturn(initState); @@ -1721,8 +1722,7 @@ public final class DisplayPowerControllerTest { mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); advanceTime(1); // Run updatePowerState - ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class); - verify(mDisplayOffloadSession).blockScreenOn(argumentCaptor.capture()); + verify(mDisplayOffloadSession).blockScreenOn(any()); // Unblocked argumentCaptor.getValue().run(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java index 0275319a40e2..ef209463c0d1 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java @@ -39,6 +39,7 @@ import android.os.BatteryConsumer; import android.os.BatteryStats; import android.os.Handler; import android.os.OutcomeReceiver; +import android.os.connectivity.CellularBatteryStats; import android.platform.test.ravenwood.RavenwoodRule; import android.telephony.AccessNetworkConstants; import android.telephony.ActivityStatsTechSpecificInfo; @@ -167,6 +168,7 @@ public class MobileRadioPowerStatsCollectorTest { public void setup() { MockitoAnnotations.initMocks(this); when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true); when(mPowerStatsUidResolver.mapUid(anyInt())).thenAnswer(invocation -> { int uid = invocation.getArgument(0); @@ -352,8 +354,48 @@ public class MobileRadioPowerStatsCollectorTest { "UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000"); } + @Test + public void getCellularBatteryStats() throws Throwable { + mBatteryStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, + true); + + mockModemActivityInfo(1000, 2000, 3000, 600, new int[]{100, 200, 300, 400, 500}); + mockNetworkStats(1100, + 5321, 421, 3234, 223, + 8000, 80, 4000, 40); + + // This should trigger a baseline sample collection + mBatteryStats.onSystemReady(mContext); + mStatsRule.waitForBackgroundThread(); + + mockModemActivityInfo(20000, 2222, 3333, 666, new int[]{111, 222, 333, 444, 555}); + mockNetworkStats(21000, + 6321, 521, 7234, 423, + 8888, 88, 4444, 44); + + mStatsRule.setTime(30000, 30000); + mBatteryStats.getPowerStatsCollector(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO) + .schedule(); + mStatsRule.waitForBackgroundThread(); + + CellularBatteryStats stats = mBatteryStats.getCellularBatteryStats(); + assertThat(stats.getSleepTimeMillis()).isEqualTo(222); + assertThat(stats.getIdleTimeMillis()).isEqualTo(333); + assertThat(stats.getRxTimeMillis()).isEqualTo(66); + assertThat(stats.getTxTimeMillis(ModemActivityInfo.TX_POWER_LEVEL_0)).isEqualTo(11); + assertThat(stats.getTxTimeMillis(ModemActivityInfo.TX_POWER_LEVEL_1)).isEqualTo(22); + assertThat(stats.getTxTimeMillis(ModemActivityInfo.TX_POWER_LEVEL_2)).isEqualTo(33); + assertThat(stats.getTxTimeMillis(ModemActivityInfo.TX_POWER_LEVEL_3)).isEqualTo(44); + assertThat(stats.getTxTimeMillis(ModemActivityInfo.TX_POWER_LEVEL_4)).isEqualTo(55); + assertThat(stats.getNumPacketsRx()).isEqualTo(934); + assertThat(stats.getNumBytesRx()).isEqualTo(19967); + assertThat(stats.getNumPacketsTx()).isEqualTo(770); + assertThat(stats.getNumBytesTx()).isEqualTo(14214); + } + private PowerStats collectPowerStats(boolean perNetworkTypeData) throws Throwable { - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); + MobileRadioPowerStatsCollector collector = + new MobileRadioPowerStatsCollector(mInjector, null); collector.setEnabled(true); when(mConsumedEnergyRetriever.getEnergyConsumerIds( @@ -462,6 +504,7 @@ public class MobileRadioPowerStatsCollectorTest { .addEntry(new NetworkStats.Entry("mobile", APP_UID3, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 314, 281, 314, 281, 111)); } + mBatteryStats.setNetworkStats(stats); when(mNetworkStatsSupplier.get()).thenReturn(stats); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java index 137c2a6a36d9..d7024e5e45fe 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java @@ -191,7 +191,8 @@ public class MobileRadioPowerStatsProcessorTest { aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0); aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0); - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); + MobileRadioPowerStatsCollector collector = + new MobileRadioPowerStatsCollector(mInjector, null); collector.setEnabled(true); // Initial empty ModemActivityInfo. @@ -430,7 +431,8 @@ public class MobileRadioPowerStatsProcessorTest { aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0); aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0); - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); + MobileRadioPowerStatsCollector collector = + new MobileRadioPowerStatsCollector(mInjector, null); collector.setEnabled(true); // Initial empty ModemActivityInfo. diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java index 548d54cc3efc..c2681106a28c 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java @@ -180,7 +180,8 @@ public class PhoneCallPowerStatsProcessorTest { aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0); aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0); - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); + MobileRadioPowerStatsCollector collector = + new MobileRadioPowerStatsCollector(mInjector, null); collector.setEnabled(true); // Initial empty ModemActivityInfo. diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java index a280cfe176a3..362607b91763 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java @@ -40,6 +40,7 @@ import android.os.BatteryStatsManager; import android.os.Handler; import android.os.WorkSource; import android.os.connectivity.WifiActivityEnergyInfo; +import android.os.connectivity.WifiBatteryStats; import android.platform.test.ravenwood.RavenwoodRule; import android.util.IndentingPrintWriter; import android.util.SparseArray; @@ -186,6 +187,7 @@ public class WifiPowerStatsCollectorTest { return uid; } }); + when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager); mBatteryStats = mStatsRule.getBatteryStats(); } @@ -319,10 +321,51 @@ public class WifiPowerStatsCollectorTest { + " scan: 234 batched-scan: 345"); } + @Test + public void getWifiBatteryStats() throws Throwable { + when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(true); + mBatteryStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_WIFI, + true); + + mockWifiActivityInfo(1000, 600, 100, 2000, 3000); + mockNetworkStats(1000); + mockNetworkStatsEntry(APP_UID1, 4321, 321, 1234, 23); + mockNetworkStatsEntry(APP_UID2, 4000, 40, 2000, 20); + mockWifiScanTimes(APP_UID1, 1000, 2000); + mockWifiScanTimes(APP_UID2, 3000, 4000); + + // This should trigger a baseline sample collection + mBatteryStats.onSystemReady(mContext); + mStatsRule.waitForBackgroundThread(); + + mockWifiActivityInfo(1100, 6600, 1100, 2200, 3300); + mockNetworkStats(1100); + mockNetworkStatsEntry(APP_UID1, 5321, 421, 3234, 223); + mockNetworkStatsEntry(APP_UID2, 8000, 80, 4000, 40); + mockWifiScanTimes(APP_UID1, 1234, 2345); + mockWifiScanTimes(APP_UID2, 3100, 4200); + + mStatsRule.setTime(30000, 30000); + mBatteryStats.getPowerStatsCollector(BatteryConsumer.POWER_COMPONENT_WIFI) + .schedule(); + mStatsRule.waitForBackgroundThread(); + + WifiBatteryStats stats = mBatteryStats.getWifiBatteryStats(); + assertThat(stats.getNumPacketsRx()).isEqualTo(501); + assertThat(stats.getNumBytesRx()).isEqualTo(13321); + assertThat(stats.getNumPacketsTx()).isEqualTo(263); + assertThat(stats.getNumBytesTx()).isEqualTo(7234); + assertThat(stats.getScanTimeMillis()).isEqualTo(2200); + assertThat(stats.getRxTimeMillis()).isEqualTo(6000); + assertThat(stats.getTxTimeMillis()).isEqualTo(1000); + assertThat(stats.getIdleTimeMillis()).isEqualTo(300); + assertThat(stats.getSleepTimeMillis()).isEqualTo(30000 - 6000 - 1000 - 300); + } + private PowerStats collectPowerStats(boolean hasPowerReporting) { when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(hasPowerReporting); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, null); collector.setEnabled(true); when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI)) @@ -389,6 +432,7 @@ public class WifiPowerStatsCollectorTest { } else { mNetworkStats = new NetworkStats(elapsedRealtime, 1); } + mBatteryStats.setNetworkStats(mNetworkStats); when(mNetworkStatsSupplier.get()).thenReturn(mNetworkStats); } @@ -411,6 +455,7 @@ public class WifiPowerStatsCollectorTest { .addEntry(new NetworkStats.Entry("wifi", uid, 0, 0, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, rxPackets, txBytes, txPackets, 100)); + mBatteryStats.setNetworkStats(mNetworkStats); reset(mNetworkStatsSupplier); when(mNetworkStatsSupplier.get()).thenReturn(mNetworkStats); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java index ff566919b7a3..7ddaefd811ff 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java @@ -99,6 +99,8 @@ public class WifiPowerStatsProcessorTest { @Mock private WifiManager mWifiManager; + private MockBatteryStatsImpl mBatteryStats; + private static class ScanTimes { public long scanTimeMs; public long batchScanTimeMs; @@ -185,6 +187,8 @@ public class WifiPowerStatsProcessorTest { when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true); when(mPowerStatsUidResolver.mapUid(anyInt())) .thenAnswer(invocation -> invocation.getArgument(0)); + + mBatteryStats = mStatsRule.getBatteryStats(); } @Test @@ -200,7 +204,7 @@ public class WifiPowerStatsProcessorTest { PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, null); collector.setEnabled(true); // Initial empty WifiActivityEnergyInfo. @@ -312,7 +316,7 @@ public class WifiPowerStatsProcessorTest { PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, null); collector.setEnabled(true); // Initial empty WifiActivityEnergyInfo. @@ -425,7 +429,7 @@ public class WifiPowerStatsProcessorTest { PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, null); collector.setEnabled(true); // Establish a baseline diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java index 0f3b0aa72b72..636cbeef27f7 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java @@ -37,6 +37,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.media.permission.INativePermissionController; +import com.android.media.permission.PermissionEnum; import com.android.media.permission.UidPackageState; import com.android.server.pm.pkg.PackageState; @@ -353,6 +354,56 @@ public final class AudioServerPermissionProviderTest { } @Test + public void testSpecialHotwordPermissions() throws Exception { + BiPredicate<Integer, String> customPermPred = mock(BiPredicate.class); + var initPackageListData = + List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); + // expected state + // PERM[CAPTURE_AUDIO_HOTWORD]: [10000] + // PERM[CAPTURE_AUDIO_OUTPUT]: [10001] + // PERM[RECORD_AUDIO]: [10001] + // PERM[...]: [] + when(customPermPred.test( + eq(10000), eq(MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_HOTWORD]))) + .thenReturn(true); + when(customPermPred.test( + eq(10001), eq(MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_OUTPUT]))) + .thenReturn(true); + when(customPermPred.test(eq(10001), eq(MONITORED_PERMS[PermissionEnum.RECORD_AUDIO]))) + .thenReturn(true); + mPermissionProvider = + new AudioServerPermissionProvider( + initPackageListData, customPermPred, () -> new int[] {0}); + int HDS_UID = 99001; + mPermissionProvider.onServiceStart(mMockPc); + clearInvocations(mMockPc); + mPermissionProvider.setIsolatedServiceUid(HDS_UID, 10000); + verify(mMockPc) + .populatePermissionState( + eq((byte) PermissionEnum.CAPTURE_AUDIO_HOTWORD), + aryEq(new int[] {10000, HDS_UID})); + verify(mMockPc) + .populatePermissionState( + eq((byte) PermissionEnum.CAPTURE_AUDIO_OUTPUT), + aryEq(new int[] {10001, HDS_UID})); + verify(mMockPc) + .populatePermissionState( + eq((byte) PermissionEnum.RECORD_AUDIO), aryEq(new int[] {10001, HDS_UID})); + + clearInvocations(mMockPc); + mPermissionProvider.clearIsolatedServiceUid(HDS_UID); + verify(mMockPc) + .populatePermissionState( + eq((byte) PermissionEnum.CAPTURE_AUDIO_HOTWORD), aryEq(new int[] {10000})); + verify(mMockPc) + .populatePermissionState( + eq((byte) PermissionEnum.CAPTURE_AUDIO_OUTPUT), aryEq(new int[] {10001})); + verify(mMockPc) + .populatePermissionState( + eq((byte) PermissionEnum.RECORD_AUDIO), aryEq(new int[] {10001})); + } + + @Test public void testPermissionsPopulated_onChange() throws Exception { var initPackageListData = List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two); diff --git a/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java b/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java index 6d56c417f789..60c3659202be 100644 --- a/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java +++ b/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java @@ -17,17 +17,25 @@ package com.android.server.autofill; import static com.google.common.truth.Truth.assertThat; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.util.ArrayList; -import java.util.List; - @RunWith(JUnit4.class) public class RequestIdTest { + private static final int TEST_DATASET_SIZE = 300; + private static final int TEST_WRAP_SIZE = 50; // Number of request ids before wrap happens + private static final String TAG = "RequestIdTest"; + List<Integer> datasetPrimaryNoWrap = new ArrayList<>(); List<Integer> datasetPrimaryWrap = new ArrayList<>(); List<Integer> datasetSecondaryNoWrap = new ArrayList<>(); @@ -35,151 +43,200 @@ public class RequestIdTest { List<Integer> datasetMixedNoWrap = new ArrayList<>(); List<Integer> datasetMixedWrap = new ArrayList<>(); - @Before - public void setup() throws Exception { - int datasetSize = 300; + List<Integer> manualWrapRequestIdList = Arrays.asList(3, 9, 15, + RequestId.MAX_SECONDARY_REQUEST_ID - 5, + RequestId.MAX_SECONDARY_REQUEST_ID - 3); + List<Integer> manualNoWrapRequestIdList =Arrays.asList(2, 6, 10, 14, 18, 22, 26, 30); + List<Integer> manualOneElementRequestIdList = Arrays.asList(1); + + @Before + public void setup() throws IllegalArgumentException { + Slog.d(TAG, "setup()"); { // Generate primary only ids that do not wrap - RequestId requestId = new RequestId(0); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MIN_PRIMARY_REQUEST_ID); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetPrimaryNoWrap.add(requestId.nextId(false)); } + Collections.sort(datasetPrimaryNoWrap); } { // Generate primary only ids that wrap - RequestId requestId = new RequestId(0xff00); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MAX_PRIMARY_REQUEST_ID - + TEST_WRAP_SIZE * 2); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetPrimaryWrap.add(requestId.nextId(false)); } + Collections.sort(datasetPrimaryWrap); } { // Generate SECONDARY only ids that do not wrap - RequestId requestId = new RequestId(0); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MIN_SECONDARY_REQUEST_ID); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetSecondaryNoWrap.add(requestId.nextId(true)); } + Collections.sort(datasetSecondaryNoWrap); } { // Generate SECONDARY only ids that wrap - RequestId requestId = new RequestId(0xff00); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MAX_SECONDARY_REQUEST_ID - + TEST_WRAP_SIZE * 2); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetSecondaryWrap.add(requestId.nextId(true)); } + Collections.sort(datasetSecondaryWrap); } { // Generate MIXED only ids that do not wrap - RequestId requestId = new RequestId(0); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MIN_REQUEST_ID); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetMixedNoWrap.add(requestId.nextId(i % 2 != 0)); } + Collections.sort(datasetMixedNoWrap); } { // Generate MIXED only ids that wrap - RequestId requestId = new RequestId(0xff00); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MAX_REQUEST_ID - + TEST_WRAP_SIZE); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetMixedWrap.add(requestId.nextId(i % 2 != 0)); } + Collections.sort(datasetMixedWrap); } + Slog.d(TAG, "finishing setup()"); } @Test public void testRequestIdLists() { + Slog.d(TAG, "testRequestIdLists()"); for (int id : datasetPrimaryNoWrap) { assertThat(RequestId.isSecondaryProvider(id)).isFalse(); - assertThat(id >= 0).isTrue(); - assertThat(id < 0xffff).isTrue(); + assertThat(id).isAtLeast(RequestId.MIN_PRIMARY_REQUEST_ID); + assertThat(id).isAtMost(RequestId.MAX_PRIMARY_REQUEST_ID); } for (int id : datasetPrimaryWrap) { assertThat(RequestId.isSecondaryProvider(id)).isFalse(); - assertThat(id >= 0).isTrue(); - assertThat(id < 0xffff).isTrue(); + assertThat(id).isAtLeast(RequestId.MIN_PRIMARY_REQUEST_ID); + assertThat(id).isAtMost(RequestId.MAX_PRIMARY_REQUEST_ID); } for (int id : datasetSecondaryNoWrap) { assertThat(RequestId.isSecondaryProvider(id)).isTrue(); - assertThat(id >= 0).isTrue(); - assertThat(id < 0xffff).isTrue(); + assertThat(id).isAtLeast(RequestId.MIN_SECONDARY_REQUEST_ID); + assertThat(id).isAtMost(RequestId.MAX_SECONDARY_REQUEST_ID); } for (int id : datasetSecondaryWrap) { assertThat(RequestId.isSecondaryProvider(id)).isTrue(); - assertThat(id >= 0).isTrue(); - assertThat(id < 0xffff).isTrue(); + assertThat(id).isAtLeast(RequestId.MIN_SECONDARY_REQUEST_ID); + assertThat(id).isAtMost(RequestId.MAX_SECONDARY_REQUEST_ID); } } @Test - public void testRequestIdGeneration() { - RequestId requestId = new RequestId(0); + public void testCreateNewRequestId() { + Slog.d(TAG, "testCreateNewRequestId()"); + for (int i = 0; i < 100000; i++) { + RequestId requestId = new RequestId(); + assertThat(requestId.getRequestId()).isAtLeast(RequestId.MIN_REQUEST_ID); + assertThat(requestId.getRequestId()).isAtMost(RequestId.MAX_START_ID); + } + } + @Test + public void testGetNextRequestId() throws IllegalArgumentException{ + Slog.d(TAG, "testGetNextRequestId()"); + RequestId requestId = new RequestId(); // Large Primary for (int i = 0; i < 100000; i++) { int y = requestId.nextId(false); assertThat(RequestId.isSecondaryProvider(y)).isFalse(); - assertThat(y >= 0).isTrue(); - assertThat(y < 0xffff).isTrue(); + assertThat(y).isAtLeast(RequestId.MIN_PRIMARY_REQUEST_ID); + assertThat(y).isAtMost(RequestId.MAX_PRIMARY_REQUEST_ID); } // Large Secondary - requestId = new RequestId(0); + requestId = new RequestId(); for (int i = 0; i < 100000; i++) { int y = requestId.nextId(true); assertThat(RequestId.isSecondaryProvider(y)).isTrue(); - assertThat(y >= 0).isTrue(); - assertThat(y < 0xffff).isTrue(); + assertThat(y).isAtLeast(RequestId.MIN_SECONDARY_REQUEST_ID); + assertThat(y).isAtMost(RequestId.MAX_SECONDARY_REQUEST_ID); } // Large Mixed - requestId = new RequestId(0); + requestId = new RequestId(); for (int i = 0; i < 50000; i++) { int y = requestId.nextId(i % 2 != 0); - assertThat(RequestId.isSecondaryProvider(y)).isEqualTo(i % 2 == 0); - assertThat(y >= 0).isTrue(); - assertThat(y < 0xffff).isTrue(); + assertThat(y).isAtLeast(RequestId.MIN_REQUEST_ID); + assertThat(y).isAtMost(RequestId.MAX_REQUEST_ID); } } @Test public void testGetLastRequestId() { - // In this test, request ids are generated FIFO, so the last entry is also the last - // request + Slog.d(TAG, "testGetLastRequestId()"); - { // Primary no wrap - int lastIdIndex = datasetPrimaryNoWrap.size() - 1; - int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryNoWrap); - assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); + { // Primary no wrap + int lastIdIndex = datasetPrimaryNoWrap.size() - 1; + int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryNoWrap); + assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); } - { // Primary wrap - int lastIdIndex = datasetPrimaryWrap.size() - 1; + { // Primary wrap + // The last index would be the # of request ids left after wrap + // minus 1 (index starts at 0) + int lastIdIndex = TEST_DATASET_SIZE - TEST_WRAP_SIZE - 1; int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryWrap); - assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); + assertThat(lastComputedIdIndex).isEqualTo(lastIdIndex); } - { // Secondary no wrap + { // Secondary no wrap int lastIdIndex = datasetSecondaryNoWrap.size() - 1; int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetSecondaryNoWrap); assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); } - { // Secondary wrap - int lastIdIndex = datasetSecondaryWrap.size() - 1; + { // Secondary wrap + int lastIdIndex = TEST_DATASET_SIZE - TEST_WRAP_SIZE - 1; int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetSecondaryWrap); assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); } - { // Mixed no wrap + { // Mixed no wrap int lastIdIndex = datasetMixedNoWrap.size() - 1; int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetMixedNoWrap); assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); } - { // Mixed wrap - int lastIdIndex = datasetMixedWrap.size() - 1; + { // Mixed wrap + int lastIdIndex = TEST_DATASET_SIZE - TEST_WRAP_SIZE - 1; int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetMixedWrap); assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); } + { // Manual wrap + int lastIdIndex = 2; // [3, 9, 15, + // MAX_SECONDARY_REQUEST_ID - 5, MAX_SECONDARY_REQUEST_ID - 3] + int lastComputedIdIndex = RequestId.getLastRequestIdIndex(manualWrapRequestIdList); + assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); + } + + { // Manual no wrap + int lastIdIndex = manualNoWrapRequestIdList.size() - 1; // [2, 6, 10, 14, + // 18, 22, 26, 30] + int lastComputedIdIndex = RequestId.getLastRequestIdIndex(manualNoWrapRequestIdList); + assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); + + } + + { // Manual one element + int lastIdIndex = 0; // [1] + int lastComputedIdIndex = RequestId.getLastRequestIdIndex( + manualOneElementRequestIdList); + assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); + + } } } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java index 067814036afb..e078238f6385 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java @@ -92,8 +92,6 @@ public class GenericWindowPolicyControllerTest { public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock - private GenericWindowPolicyController.PipBlockedCallback mPipBlockedCallback; - @Mock private VirtualDeviceManager.ActivityListener mActivityListener; @Mock private GenericWindowPolicyController.IntentListenerCallback mIntentListenerCallback; @@ -140,7 +138,6 @@ public class GenericWindowPolicyControllerTest { gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false); assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isFalse(); - verify(mPipBlockedCallback, timeout(TIMEOUT_MILLIS)).onEnteringPipBlocked(TEST_UID); } @Test @@ -151,7 +148,6 @@ public class GenericWindowPolicyControllerTest { Arrays.asList(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, WindowConfiguration.WINDOWING_MODE_PINNED))); assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isTrue(); - verify(mPipBlockedCallback, after(TIMEOUT_MILLIS).never()).onEnteringPipBlocked(TEST_UID); } @Test @@ -746,7 +742,6 @@ public class GenericWindowPolicyControllerTest { /* crossTaskNavigationExemptions= */ new ArraySet<>(), /* permissionDialogComponent= */ null, /* activityListener= */ mActivityListener, - /* pipBlockedCallback= */ mPipBlockedCallback, /* activityBlockedCallback= */ mActivityBlockedCallback, /* secureWindowCallback= */ mSecureWindowCallback, /* intentListenerCallback= */ mIntentListenerCallback, @@ -767,7 +762,6 @@ public class GenericWindowPolicyControllerTest { /* crossTaskNavigationExemptions= */ new ArraySet<>(), /* permissionDialogComponent= */ null, /* activityListener= */ mActivityListener, - /* pipBlockedCallback= */ mPipBlockedCallback, /* activityBlockedCallback= */ mActivityBlockedCallback, /* secureWindowCallback= */ mSecureWindowCallback, /* intentListenerCallback= */ mIntentListenerCallback, @@ -789,7 +783,6 @@ public class GenericWindowPolicyControllerTest { /* crossTaskNavigationExemptions= */ new ArraySet<>(), /* permissionDialogComponent= */ null, /* activityListener= */ mActivityListener, - /* pipBlockedCallback= */ mPipBlockedCallback, /* activityBlockedCallback= */ mActivityBlockedCallback, /* secureWindowCallback= */ null, /* intentListenerCallback= */ mIntentListenerCallback, @@ -811,7 +804,6 @@ public class GenericWindowPolicyControllerTest { /* crossTaskNavigationExemptions= */ new ArraySet<>(), /* permissionDialogComponent= */ null, /* activityListener= */ mActivityListener, - /* pipBlockedCallback= */ mPipBlockedCallback, /* activityBlockedCallback= */ mActivityBlockedCallback, /* secureWindowCallback= */ null, /* intentListenerCallback= */ mIntentListenerCallback, @@ -833,7 +825,6 @@ public class GenericWindowPolicyControllerTest { /* crossTaskNavigationExemptions= */ new ArraySet<>(), /* permissionDialogComponent= */ null, /* activityListener= */ mActivityListener, - /* pipBlockedCallback= */ mPipBlockedCallback, /* activityBlockedCallback= */ mActivityBlockedCallback, /* secureWindowCallback= */ null, /* intentListenerCallback= */ mIntentListenerCallback, @@ -855,7 +846,6 @@ public class GenericWindowPolicyControllerTest { /* crossTaskNavigationExemptions= */ new ArraySet<>(), /* permissionDialogComponent= */ null, /* activityListener= */ mActivityListener, - /* pipBlockedCallback= */ mPipBlockedCallback, /* activityBlockedCallback= */ mActivityBlockedCallback, /* secureWindowCallback= */ null, /* intentListenerCallback= */ mIntentListenerCallback, @@ -877,7 +867,6 @@ public class GenericWindowPolicyControllerTest { /* crossTaskNavigationExemptions= */ Collections.singleton(blockedComponent), /* permissionDialogComponent= */ null, /* activityListener= */ mActivityListener, - /* pipBlockedCallback= */ mPipBlockedCallback, /* activityBlockedCallback= */ mActivityBlockedCallback, /* secureWindowCallback= */ null, /* intentListenerCallback= */ mIntentListenerCallback, @@ -899,7 +888,6 @@ public class GenericWindowPolicyControllerTest { /* crossTaskNavigationExemptions= */ Collections.singleton(allowedComponent), /* permissionDialogComponent= */ null, /* activityListener= */ mActivityListener, - /* pipBlockedCallback= */ mPipBlockedCallback, /* activityBlockedCallback= */ mActivityBlockedCallback, /* secureWindowCallback= */ null, /* intentListenerCallback= */ mIntentListenerCallback, @@ -922,7 +910,6 @@ public class GenericWindowPolicyControllerTest { /* crossTaskNavigationExemptions= */ new ArraySet<>(), /* permissionDialogComponent= */ permissionComponent, /* activityListener= */ mActivityListener, - /* pipBlockedCallback= */ mPipBlockedCallback, /* activityBlockedCallback= */ mActivityBlockedCallback, /* secureWindowCallback= */ null, /* intentListenerCallback= */ mIntentListenerCallback, diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java index 52f28b9bdc50..b946a43ea038 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java @@ -87,7 +87,6 @@ public class VirtualAudioControllerTest { /* crossTaskNavigationExemptions= */ new ArraySet<>(), /* permissionDialogComponent */ null, /* activityListener= */ null, - /* pipBlockedCallback= */ null, /* activityBlockedCallback= */ null, /* secureWindowCallback= */ null, /* intentListenerCallback= */ null, diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp index b2922945aff9..6ba2c7010cf3 100644 --- a/services/tests/wmtests/Android.bp +++ b/services/tests/wmtests/Android.bp @@ -28,7 +28,7 @@ genrule { ], tools: ["protologtool"], cmd: "$(location protologtool) transform-protolog-calls " + - "--protolog-class com.android.internal.protolog.common.ProtoLog " + + "--protolog-class com.android.internal.protolog.ProtoLog " + "--loggroups-class com.android.internal.protolog.ProtoLogGroup " + "--loggroups-jar $(location :protolog-groups) " + // Used for the ProtoLogIntegrationTest, where don't test decoding or writing to file diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java index 7d9fdd507235..3fcf3042ab94 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java @@ -24,9 +24,12 @@ import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import static org.testng.Assert.assertFalse; import android.annotation.Nullable; @@ -55,6 +58,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.util.function.Consumer; /** * Tests for the {@link DisplayWindowSettingsProvider} class. @@ -128,9 +132,8 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase { // Update settings with new value, should trigger write to injector. DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider( mDefaultVendorSettingsStorage, mOverrideSettingsStorage); - SettingsEntry overrideSettings = provider.getOverrideSettings(mPrimaryDisplayInfo); - overrideSettings.mForcedDensity = 200; - provider.updateOverrideSettings(mPrimaryDisplayInfo, overrideSettings); + updateOverrideSettings(provider, mPrimaryDisplayInfo, + overrideSettings -> overrideSettings.mForcedDensity = 200); assertTrue(mOverrideSettingsStorage.wasWriteSuccessful()); // Verify that display identifier was updated. @@ -167,7 +170,7 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase { } @Test - public void testReadingDisplaySettingsFromStorage_secondayVendorDisplaySettingsLocation() { + public void testReadingDisplaySettingsFromStorage_secondaryVendorDisplaySettingsLocation() { final String displayIdentifier = mSecondaryDisplay.getDisplayInfo().uniqueId; prepareSecondaryDisplaySettings(displayIdentifier); @@ -216,11 +219,11 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase { // Write some settings to storage. DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider( mDefaultVendorSettingsStorage, mOverrideSettingsStorage); - SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo); - overrideSettings.mShouldShowSystemDecors = true; - overrideSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL; - overrideSettings.mDontMoveToTop = true; - provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings); + updateOverrideSettings(provider, secondaryDisplayInfo, overrideSettings -> { + overrideSettings.mShouldShowSystemDecors = true; + overrideSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL; + overrideSettings.mDontMoveToTop = true; + }); assertTrue(mOverrideSettingsStorage.wasWriteSuccessful()); // Verify that settings were stored correctly. @@ -235,6 +238,29 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase { } @Test + public void testWritingDisplaySettingsToStorage_secondaryUserDisplaySettingsLocation() { + final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider( + mDefaultVendorSettingsStorage, mOverrideSettingsStorage); + final DisplayInfo displayInfo = mPrimaryDisplay.getDisplayInfo(); + final TestStorage secondaryUserOverrideSettingsStorage = new TestStorage(); + final SettingsEntry expectedSettings = new SettingsEntry(); + expectedSettings.mForcedDensity = 356; + + // Write some settings to storage from default user. + updateOverrideSettings(provider, displayInfo, settings -> settings.mForcedDensity = 356); + assertThat(mOverrideSettingsStorage.wasWriteSuccessful()).isTrue(); + + // Now switch to secondary user override settings and write some settings. + provider.setOverrideSettingsStorage(secondaryUserOverrideSettingsStorage); + updateOverrideSettings(provider, displayInfo, settings -> settings.mForcedDensity = 420); + assertThat(secondaryUserOverrideSettingsStorage.wasWriteSuccessful()).isTrue(); + + // Switch back to primary and assert default user settings remain unchanged. + provider.setOverrideSettingsStorage(mOverrideSettingsStorage); + assertThat(provider.getOverrideSettings(displayInfo)).isEqualTo(expectedSettings); + } + + @Test public void testDoNotWriteVirtualDisplaySettingsToStorage() throws Exception { final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo(); secondaryDisplayInfo.type = TYPE_VIRTUAL; @@ -242,11 +268,11 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase { // No write to storage on virtual display change. final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider( mDefaultVendorSettingsStorage, mOverrideSettingsStorage); - final SettingsEntry virtualSettings = provider.getOverrideSettings(secondaryDisplayInfo); - virtualSettings.mShouldShowSystemDecors = true; - virtualSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL; - virtualSettings.mDontMoveToTop = true; - provider.updateOverrideSettings(secondaryDisplayInfo, virtualSettings); + updateOverrideSettings(provider, secondaryDisplayInfo, virtualSettings -> { + virtualSettings.mShouldShowSystemDecors = true; + virtualSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL; + virtualSettings.mDontMoveToTop = true; + }); assertFalse(mOverrideSettingsStorage.wasWriteSuccessful()); } @@ -263,10 +289,10 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase { // Write some settings to storage. DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider( mDefaultVendorSettingsStorage, mOverrideSettingsStorage); - SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo); - overrideSettings.mShouldShowSystemDecors = true; - overrideSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL; - provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings); + updateOverrideSettings(provider, secondaryDisplayInfo, overrideSettings -> { + overrideSettings.mShouldShowSystemDecors = true; + overrideSettings.mImePolicy = DISPLAY_IME_POLICY_LOCAL; + }); assertTrue(mOverrideSettingsStorage.wasWriteSuccessful()); // Verify that settings were stored correctly. @@ -283,16 +309,16 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase { final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider( mDefaultVendorSettingsStorage, mOverrideSettingsStorage); final int initialSize = provider.getOverrideSettingsSize(); - - // Size + 1 when query for a new display. final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo(); - final SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo); - assertEquals(initialSize + 1, provider.getOverrideSettingsSize()); + updateOverrideSettings(provider, secondaryDisplayInfo, overrideSettings -> { + // Size + 1 when query for a new display. + assertEquals(initialSize + 1, provider.getOverrideSettingsSize()); - // When a display is removed, its override Settings is not removed if there is any override. - overrideSettings.mShouldShowSystemDecors = true; - provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings); + // When a display is removed, its override Settings is not removed if there is any + // override. + overrideSettings.mShouldShowSystemDecors = true; + }); provider.onDisplayRemoved(secondaryDisplayInfo); assertEquals(initialSize + 1, provider.getOverrideSettingsSize()); @@ -309,23 +335,53 @@ public class DisplayWindowSettingsProviderTests extends WindowTestsBase { final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider( mDefaultVendorSettingsStorage, mOverrideSettingsStorage); final int initialSize = provider.getOverrideSettingsSize(); - - // Size + 1 when query for a new display. final DisplayInfo secondaryDisplayInfo = mSecondaryDisplay.getDisplayInfo(); secondaryDisplayInfo.type = TYPE_VIRTUAL; - final SettingsEntry overrideSettings = provider.getOverrideSettings(secondaryDisplayInfo); - assertEquals(initialSize + 1, provider.getOverrideSettingsSize()); + updateOverrideSettings(provider, secondaryDisplayInfo, overrideSettings -> { + // Size + 1 when query for a new display. + assertEquals(initialSize + 1, provider.getOverrideSettingsSize()); - // When a virtual display is removed, its override Settings is removed even if it has - // override. - overrideSettings.mShouldShowSystemDecors = true; - provider.updateOverrideSettings(secondaryDisplayInfo, overrideSettings); + // When a virtual display is removed, its override Settings is removed + // even if it has override. + overrideSettings.mShouldShowSystemDecors = true; + }); provider.onDisplayRemoved(secondaryDisplayInfo); assertEquals(initialSize, provider.getOverrideSettingsSize()); } + @Test + public void testRemovesStaleDisplaySettings() { + assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings()); + + final DisplayWindowSettingsProvider provider = + new DisplayWindowSettingsProvider(mDefaultVendorSettingsStorage, + mOverrideSettingsStorage); + final DisplayInfo displayInfo = mSecondaryDisplay.getDisplayInfo(); + updateOverrideSettings(provider, displayInfo, settings -> settings.mForcedDensity = 356); + mRootWindowContainer.removeChild(mSecondaryDisplay); + + provider.removeStaleDisplaySettings(mRootWindowContainer); + + assertThat(mOverrideSettingsStorage.wasWriteSuccessful()).isTrue(); + assertThat(provider.getOverrideSettingsSize()).isEqualTo(0); + } + + /** + * Updates the override settings for a specific display. + * + * @param provider the provider to obtain and update the settings from. + * @param displayInfo the information about the display to be updated. + * @param modifier a function that modifies the settings for the display. + */ + private static void updateOverrideSettings(DisplayWindowSettingsProvider provider, + DisplayInfo displayInfo, Consumer<SettingsEntry> modifier) { + final SettingsEntry settings = provider.getOverrideSettings(displayInfo); + modifier.accept(settings); + provider.updateOverrideSettings(displayInfo, settings); + } + /** * Prepares display settings and stores in {@link #mOverrideSettingsStorage}. Uses provided * display identifier and stores windowingMode=WINDOWING_MODE_PINNED. diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java index c5bf78bb60b5..7efbc88833cf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java @@ -29,7 +29,7 @@ import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.ProtoLogImpl; import com.android.internal.protolog.common.IProtoLog; import com.android.internal.protolog.common.LogLevel; -import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.protolog.ProtoLog; import org.junit.After; import org.junit.Ignore; diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index eb79118fe1c7..3078df026d8a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -392,6 +392,8 @@ public class RootWindowContainerTests extends WindowTestsBase { assertEquals(newPipTask, mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask()); assertNotEquals(newPipTask, activity1.getTask()); assertFalse("Created PiP task must not be in recents", newPipTask.inRecents); + assertThat(newPipTask.autoRemoveRecents).isTrue(); + assertThat(activity1.getTask().autoRemoveRecents).isFalse(); } /** @@ -427,6 +429,7 @@ public class RootWindowContainerTests extends WindowTestsBase { bounds.scale(0.5f); task.setBounds(bounds); assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio()); + assertThat(task.autoRemoveRecents).isFalse(); } /** @@ -451,6 +454,7 @@ public class RootWindowContainerTests extends WindowTestsBase { // Ensure a task has moved over. ensureTaskPlacement(task, activity); assertTrue(task.inPinnedWindowingMode()); + assertThat(task.autoRemoveRecents).isFalse(); } /** @@ -480,6 +484,8 @@ public class RootWindowContainerTests extends WindowTestsBase { ensureTaskPlacement(fullscreenTask, secondActivity); assertTrue(pinnedRootTask.inPinnedWindowingMode()); assertEquals(WINDOWING_MODE_FULLSCREEN, fullscreenTask.getWindowingMode()); + assertThat(pinnedRootTask.autoRemoveRecents).isTrue(); + assertThat(secondActivity.getTask().autoRemoveRecents).isFalse(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index e01cea3d62f8..ef0aa9ef7666 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -42,6 +42,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.server.policy.WindowManagerPolicy.USER_ROTATION_FREE; +import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; @@ -222,6 +223,27 @@ public class TaskTests extends WindowTestsBase { } @Test + public void testReparentPinnedActivityBackToOriginalTask() { + final ActivityRecord activityMain = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final Task originalTask = activityMain.getTask(); + final ActivityRecord activityPip = new ActivityBuilder(mAtm).setTask(originalTask).build(); + activityPip.setState(RESUMED, "test"); + mAtm.mRootWindowContainer.moveActivityToPinnedRootTask(activityPip, + null /* launchIntoPipHostActivity */, "test"); + final Task pinnedActivityTask = activityPip.getTask(); + + // Simulate pinnedActivityTask unintentionally added to recent during top activity resume. + mAtm.getRecentTasks().getRawTasks().add(pinnedActivityTask); + + // Reparent the activity back to its original task when exiting PIP mode. + pinnedActivityTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + assertThat(activityPip.getTask()).isEqualTo(originalTask); + assertThat(originalTask.autoRemoveRecents).isFalse(); + assertThat(mAtm.getRecentTasks().getRawTasks()).containsExactly(originalTask); + } + + @Test public void testReparent_BetweenDisplays() { // Create first task on primary display. final Task rootTask1 = createTask(mDisplayContent); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 27c383c283a1..bf46154caf3e 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -113,6 +113,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.pm.UserManagerInternal; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import com.android.server.utils.AlarmQueue; @@ -1063,6 +1064,18 @@ public class UsageStatsService extends SystemService implements synchronized (mReportedEvents) { LinkedList<Event> events = mReportedEvents.get(userId); if (events == null) { + // TODO (b/347644400): callers of this API should verify that the userId passed to + // this method exists - there is currently a known case where USER_ALL is passed + // here and it would be added to the queue, never to be flushed correctly. The logic + // below should only remain as a last-resort catch-all fix. + final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); + if (umi == null || (umi != null && !umi.exists(userId))) { + // The userId passed is a non-existent user so don't report the event. + Slog.wtf(TAG, "Attempted to report event for non-existent user " + userId + + " (" + event.mPackage + "/" + event.mClass + + " eventType:" + event.mEventType + ")"); + return; + } events = new LinkedList<>(); mReportedEvents.put(userId, events); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index cfcc04b10107..89b98509de34 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -1165,7 +1165,7 @@ final class HotwordDetectionConnection { LocalServices.getService(PermissionManagerServiceInternal.class) .setHotwordDetectionServiceProvider(() -> uid); mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid); - addServiceUidForAudioPolicy(uid); + addServiceUidForAudioPolicy(uid, mVoiceInteractionServiceUid); } })); } @@ -1187,23 +1187,17 @@ final class HotwordDetectionConnection { }); } - private void addServiceUidForAudioPolicy(int uid) { - mScheduledExecutorService.execute(() -> { - AudioManagerInternal audioManager = - LocalServices.getService(AudioManagerInternal.class); - if (audioManager != null) { - audioManager.addAssistantServiceUid(uid); - } - }); + private void addServiceUidForAudioPolicy(int isolatedUid, int owningUid) { + AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); + if (audioManager != null) { + audioManager.addAssistantServiceUid(isolatedUid, owningUid); + } } private void removeServiceUidForAudioPolicy(int uid) { - mScheduledExecutorService.execute(() -> { - AudioManagerInternal audioManager = - LocalServices.getService(AudioManagerInternal.class); - if (audioManager != null) { - audioManager.removeAssistantServiceUid(uid); - } - }); + AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class); + if (audioManager != null) { + audioManager.removeAssistantServiceUid(uid); + } } } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 09cb464198b5..61698dbebe7c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9981,6 +9981,18 @@ public class CarrierConfigManager { "carrier_roaming_satellite_default_services_int_array"; /** + * Indicate whether carrier roaming to satellite is using ESOS (Emergency SOS) which connects + * to an emergency provider instead of PSAP (Public Safety Answering Point) for emergency + * messaging. + * + * This will need agreement with carriers before enabling this flag. + * + * The default value is false. + */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool"; + + /** * Indicating whether DUN APN should be disabled when the device is roaming. In that case, * the default APN (i.e. internet) will be used for tethering. * @@ -11137,6 +11149,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, false); sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT, (int) TimeUnit.SECONDS.toMillis(30)); + sDefaults.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false); sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, ""); sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false); sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false); diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 58488d1900fe..1089602934fd 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -274,6 +274,11 @@ public class SubscriptionInfo implements Parcelable { private final int mServiceCapabilities; /** + * Whether the carrier roaming to satellite is using ESOS for emergency messaging. + */ + private final boolean mIsSatelliteESOSSupported; + + /** * @hide * * @deprecated Use {@link SubscriptionInfo.Builder}. @@ -400,6 +405,7 @@ public class SubscriptionInfo implements Parcelable { this.mIsOnlyNonTerrestrialNetwork = false; this.mServiceCapabilities = 0; this.mTransferStatus = 0; + this.mIsSatelliteESOSSupported = false; } /** @@ -441,6 +447,7 @@ public class SubscriptionInfo implements Parcelable { this.mIsOnlyNonTerrestrialNetwork = builder.mIsOnlyNonTerrestrialNetwork; this.mServiceCapabilities = builder.mServiceCapabilities; this.mTransferStatus = builder.mTransferStatus; + this.mIsSatelliteESOSSupported = builder.mIsSatelliteESOSSupported; } /** @@ -898,6 +905,19 @@ public class SubscriptionInfo implements Parcelable { return mIsOnlyNonTerrestrialNetwork; } + + /** + * Checks if the subscription is supported ESOS over Carrier Roaming NB-IOT Satellite. + * + * @return {@code true} if the subscription supports ESOS over Carrier Roaming NB-IOT Satellite, + * {@code false} otherwise. + * @hide + */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + public boolean isSatelliteESOSSupported() { + return mIsSatelliteESOSSupported; + } + // TODO(b/316183370): replace @code with @link in javadoc after feature is released /** * Retrieves the service capabilities for the current subscription. @@ -989,6 +1009,7 @@ public class SubscriptionInfo implements Parcelable { .setServiceCapabilities( SubscriptionManager.getServiceCapabilitiesSet(source.readInt())) .setTransferStatus(source.readInt()) + .setSatelliteESOSSupported(source.readBoolean()) .build(); } @@ -1033,6 +1054,7 @@ public class SubscriptionInfo implements Parcelable { dest.writeBoolean(mIsOnlyNonTerrestrialNetwork); dest.writeInt(mServiceCapabilities); dest.writeInt(mTransferStatus); + dest.writeBoolean(mIsSatelliteESOSSupported); } @Override @@ -1099,6 +1121,7 @@ public class SubscriptionInfo implements Parcelable { + " serviceCapabilities=" + SubscriptionManager.getServiceCapabilitiesSet( mServiceCapabilities).toString() + " transferStatus=" + mTransferStatus + + " isSatelliteESOSSupported=" + mIsSatelliteESOSSupported + "]"; } @@ -1126,7 +1149,8 @@ public class SubscriptionInfo implements Parcelable { && mCountryIso.equals(that.mCountryIso) && mGroupOwner.equals(that.mGroupOwner) && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork && mServiceCapabilities == that.mServiceCapabilities - && mTransferStatus == that.mTransferStatus; + && mTransferStatus == that.mTransferStatus + && mIsSatelliteESOSSupported == that.mIsSatelliteESOSSupported; } @Override @@ -1136,7 +1160,7 @@ public class SubscriptionInfo implements Parcelable { mCardString, mIsOpportunistic, mGroupUuid, mCountryIso, mCarrierId, mProfileClass, mType, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting, mCardId, mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities, - mTransferStatus); + mTransferStatus, mIsSatelliteESOSSupported); result = 31 * result + Arrays.hashCode(mEhplmns); result = 31 * result + Arrays.hashCode(mHplmns); result = 31 * result + Arrays.hashCode(mNativeAccessRules); @@ -1346,6 +1370,11 @@ public class SubscriptionInfo implements Parcelable { * Service capabilities bitmasks the subscription supports. */ private int mServiceCapabilities = 0; + /** + * {@code true} if the subscription supports ESOS over Carrier Roaming NB-IOT Satellite. + * {@code false} otherwise. + */ + private boolean mIsSatelliteESOSSupported = false; /** * Default constructor. @@ -1392,6 +1421,7 @@ public class SubscriptionInfo implements Parcelable { mIsOnlyNonTerrestrialNetwork = info.mIsOnlyNonTerrestrialNetwork; mServiceCapabilities = info.mServiceCapabilities; mTransferStatus = info.mTransferStatus; + mIsSatelliteESOSSupported = info.mIsSatelliteESOSSupported; } /** @@ -1828,6 +1858,21 @@ public class SubscriptionInfo implements Parcelable { } /** + * Set whether the subscription is supported ESOS over Carrier Roaming NB-IOT Satellite or + * not. + * + * @param isSatelliteESOSSupported {@code true} if the subscription supports ESOS over + * Carrier Roaming NB-IOT Satellite, {@code false} otherwise. + * @return The builder. + */ + @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) + @NonNull + public Builder setSatelliteESOSSupported(boolean isSatelliteESOSSupported) { + mIsSatelliteESOSSupported = isSatelliteESOSSupported; + return this; + } + + /** * Build the {@link SubscriptionInfo}. * * @return The {@link SubscriptionInfo} instance. diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 76b4e0052792..dea10b70b7b9 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1127,7 +1127,7 @@ public class SubscriptionManager { * <P>Type: INTEGER (int)</P> * @hide */ - public static final String IS_NTN = SimInfo.COLUMN_IS_NTN; + public static final String IS_ONLY_NTN = SimInfo.COLUMN_IS_ONLY_NTN; /** * TelephonyProvider column name to identify service capabilities. @@ -1167,6 +1167,16 @@ public class SubscriptionManager { public static final String SATELLITE_ENTITLEMENT_PLMNS = SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS; + /** + * TelephonyProvider column name to indicate the satellite ESOS supported. The value of this + * column is set based on {@link CarrierConfigManager#KEY_SATELLITE_ESOS_SUPPORTED_BOOL}. + * By default, it's disabled. + * <P>Type: INTEGER (int)</P> + * + * @hide + */ + public static final String SATELLITE_ESOS_SUPPORTED = SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"USAGE_SETTING_"}, @@ -2783,17 +2793,17 @@ public class SubscriptionManager { return phoneId >= 0 && phoneId < TelephonyManager.getDefault().getActiveModemCount(); } - /** @hide */ + /** + * Puts phone ID and subscription ID into the {@code intent}. + * + * <p>If the subscription ID is not valid, only puts phone ID into the {@code intent}. + * + * @hide + */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId) { int subId = SubscriptionManager.getSubscriptionId(phoneId); - if (isValidSubscriptionId(subId)) { - putPhoneIdAndSubIdExtra(intent, phoneId, subId); - } else { - logd("putPhoneIdAndSubIdExtra: no valid subs"); - intent.putExtra(PhoneConstants.PHONE_KEY, phoneId); - intent.putExtra(EXTRA_SLOT_INDEX, phoneId); - } + putPhoneIdAndMaybeSubIdExtra(intent, phoneId, subId); } /** @hide */ @@ -2806,6 +2816,23 @@ public class SubscriptionManager { } /** + * Puts phone ID and subscription ID into the {@code intent}. + * + * <p>If the subscription ID is not valid, only puts phone ID into the {@code intent}. + * + * @hide + */ + public static void putPhoneIdAndMaybeSubIdExtra(Intent intent, int phoneId, int subId) { + if (isValidSubscriptionId(subId)) { + putPhoneIdAndSubIdExtra(intent, phoneId, subId); + } else { + if (VDBG) logd("putPhoneIdAndMaybeSubIdExtra: invalid subId"); + intent.putExtra(PhoneConstants.PHONE_KEY, phoneId); + intent.putExtra(EXTRA_SLOT_INDEX, phoneId); + } + } + + /** * Get visible subscription Id(s) of the currently active SIM(s). * * @return the list of subId's that are active, diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 2a359cd56d1b..6caed14bc31d 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -371,6 +371,24 @@ public final class SatelliteManager { @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24; + /** + * Telephony framework needs to access the current location of the device to perform the + * request. However, location in the settings is disabled by users. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_RESULT_LOCATION_DISABLED = 25; + + /** + * Telephony framework needs to access the current location of the device to perform the + * request. However, Telephony fails to fetch the current location from location service. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_RESULT_LOCATION_NOT_AVAILABLE = 26; + /** @hide */ @IntDef(prefix = {"SATELLITE_RESULT_"}, value = { SATELLITE_RESULT_SUCCESS, @@ -397,7 +415,9 @@ public final class SatelliteManager { SATELLITE_RESULT_REQUEST_IN_PROGRESS, SATELLITE_RESULT_MODEM_BUSY, SATELLITE_RESULT_ILLEGAL_STATE, - SATELLITE_RESULT_MODEM_TIMEOUT + SATELLITE_RESULT_MODEM_TIMEOUT, + SATELLITE_RESULT_LOCATION_DISABLED, + SATELLITE_RESULT_LOCATION_NOT_AVAILABLE }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteResult {} diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt index 0f1373c34ce6..2d5b50b528a3 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt @@ -17,7 +17,6 @@ package com.android.protolog.tool import com.android.internal.protolog.common.LogLevel -import com.android.internal.protolog.common.ProtoLog import com.android.internal.protolog.common.ProtoLogToolInjected import com.android.protolog.tool.CommandOptions.Companion.USAGE import com.github.javaparser.ParseProblemException @@ -61,6 +60,8 @@ object ProtoLogTool { const val PROTOLOG_IMPL_SRC_PATH = "frameworks/base/core/java/com/android/internal/protolog/ProtoLogImpl.java" + private const val PROTOLOG_CLASS_NAME = "ProtoLog"; // ProtoLog::class.java.simpleName + data class LogCall( val messageString: String, val logLevel: LogLevel, @@ -124,7 +125,7 @@ object ProtoLogTool { val text = injector.readText(file) val outSrc = try { val code = tryParse(text, path) - if (containsProtoLogText(text, ProtoLog::class.java.simpleName)) { + if (containsProtoLogText(text, PROTOLOG_CLASS_NAME)) { transformer.processClass(text, path, packagePath(file, code), code) } else { text diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt index 822118cc5343..2a8367787ba1 100644 --- a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt @@ -35,7 +35,7 @@ class EndToEndTest { val output = run( srcs = mapOf("frameworks/base/org/example/Example.java" to """ package org.example; - import com.android.internal.protolog.common.ProtoLog; + import com.android.internal.protolog.ProtoLog; import static com.android.internal.protolog.ProtoLogGroup.GROUP; class Example { @@ -48,7 +48,7 @@ class EndToEndTest { """.trimIndent()), logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"), commandOptions = CommandOptions(arrayOf("transform-protolog-calls", - "--protolog-class", "com.android.internal.protolog.common.ProtoLog", + "--protolog-class", "com.android.internal.protolog.ProtoLog", "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup", "--loggroups-jar", "not_required.jar", "--viewer-config-file-path", "not_required.pb", @@ -69,7 +69,7 @@ class EndToEndTest { val output = run( srcs = mapOf("frameworks/base/org/example/Example.java" to """ package org.example; - import com.android.internal.protolog.common.ProtoLog; + import com.android.internal.protolog.ProtoLog; import static com.android.internal.protolog.ProtoLogGroup.GROUP; class Example { @@ -82,7 +82,7 @@ class EndToEndTest { """.trimIndent()), logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"), commandOptions = CommandOptions(arrayOf("generate-viewer-config", - "--protolog-class", "com.android.internal.protolog.common.ProtoLog", + "--protolog-class", "com.android.internal.protolog.ProtoLog", "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup", "--loggroups-jar", "not_required.jar", "--viewer-config-type", "json", diff --git a/wifi/java/src/android/net/wifi/WifiMigration.java b/wifi/java/src/android/net/wifi/WifiMigration.java index 4fabc0b0babc..1a20a12898e2 100644 --- a/wifi/java/src/android/net/wifi/WifiMigration.java +++ b/wifi/java/src/android/net/wifi/WifiMigration.java @@ -19,16 +19,23 @@ package android.net.wifi; import static android.os.Environment.getDataMiscCeDirectory; import static android.os.Environment.getDataMiscDirectory; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; +import android.net.wifi.flags.Flags; +import android.os.Binder; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; +import android.os.ServiceSpecificException; import android.os.UserHandle; import android.provider.Settings; +import android.security.legacykeystore.ILegacyKeystore; import android.util.AtomicFile; +import android.util.Log; import android.util.SparseArray; import java.io.File; @@ -36,7 +43,11 @@ import java.io.FileNotFoundException; import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; import java.util.Objects; +import java.util.Set; /** * Class used to provide one time hooks for existing OEM devices to migrate their config store @@ -45,6 +56,8 @@ import java.util.Objects; */ @SystemApi public final class WifiMigration { + private static final String TAG = "WifiMigration"; + /** * Directory to read the wifi config store files from under. */ @@ -555,4 +568,49 @@ public final class WifiMigration { return data; } + + /** + * Migrate any certificates in Legacy Keystore to the newer WifiBlobstore database. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static void migrateLegacyKeystoreToWifiBlobstore() { + final long identity = Binder.clearCallingIdentity(); + try { + ILegacyKeystore legacyKeystore = WifiBlobStore.getLegacyKeystore(); + String[] legacyAliases = legacyKeystore.list("", Process.WIFI_UID); + if (legacyAliases == null || legacyAliases.length == 0) { + Log.i(TAG, "No aliases need to be migrated"); + return; + } + + WifiBlobStore wifiBlobStore = WifiBlobStore.getInstance(); + List<String> blobstoreAliasList = Arrays.asList(wifiBlobStore.list("")); + Set<String> blobstoreAliases = new HashSet<>(); + blobstoreAliases.addAll(blobstoreAliasList); + + for (String legacyAlias : legacyAliases) { + // Only migrate if the alias is not already in WifiBlobstore, + // since WifiBlobstore should already contain the latest value. + if (!blobstoreAliases.contains(legacyAlias)) { + byte[] value = legacyKeystore.get(legacyAlias, Process.WIFI_UID); + wifiBlobStore.put(legacyAlias, value); + } + legacyKeystore.remove(legacyAlias, Process.WIFI_UID); + } + Log.i(TAG, "Successfully migrated aliases from Legacy Keystore"); + } catch (ServiceSpecificException e) { + if (e.errorCode == ILegacyKeystore.ERROR_SYSTEM_ERROR) { + Log.i(TAG, "Legacy Keystore service has been deprecated"); + } else { + Log.e(TAG, "Encountered an exception while migrating aliases. " + e); + } + } catch (Exception e) { + Log.e(TAG, "Encountered an exception while migrating aliases. " + e); + } finally { + Binder.restoreCallingIdentity(identity); + } + } } diff --git a/wifi/tests/src/android/net/wifi/WifiMigrationTest.java b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java new file mode 100644 index 000000000000..8a5912f0ffdf --- /dev/null +++ b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.validateMockitoUsage; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import android.security.legacykeystore.ILegacyKeystore; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; + +/** + * Unit tests for {@link WifiMigration}. + */ +public class WifiMigrationTest { + public static final String TEST_ALIAS = "someAliasString"; + public static final byte[] TEST_VALUE = new byte[]{10, 11, 12}; + + @Mock private ILegacyKeystore mLegacyKeystore; + @Mock private WifiBlobStore mWifiBlobStore; + + private MockitoSession mSession; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mSession = ExtendedMockito.mockitoSession() + .mockStatic(WifiBlobStore.class, withSettings().lenient()) + .startMocking(); + when(WifiBlobStore.getLegacyKeystore()).thenReturn(mLegacyKeystore); + when(WifiBlobStore.getInstance()).thenReturn(mWifiBlobStore); + when(mLegacyKeystore.get(anyString(), anyInt())).thenReturn(TEST_VALUE); + } + + @After + public void cleanup() { + validateMockitoUsage(); + if (mSession != null) { + mSession.finishMocking(); + } + } + + /** + * Verify that the Keystore migration method returns immediately if no aliases + * are found in Legacy Keystore. + */ + @Test + public void testKeystoreMigrationNoLegacyAliases() throws Exception { + when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(new String[0]); + WifiMigration.migrateLegacyKeystoreToWifiBlobstore(); + verify(mLegacyKeystore).list(anyString(), anyInt()); + verifyNoMoreInteractions(mLegacyKeystore, mWifiBlobStore); + } + + /** + * Verify that if all aliases in Legacy Keystore are unique to that database, + * all aliases are migrated to WifiBlobstore. + */ + @Test + public void testKeystoreMigrationUniqueLegacyAliases() throws Exception { + String[] legacyAliases = new String[]{TEST_ALIAS + "1", TEST_ALIAS + "2"}; + String[] blobstoreAliases = new String[0]; + when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(legacyAliases); + when(mWifiBlobStore.list(anyString())).thenReturn(blobstoreAliases); + + WifiMigration.migrateLegacyKeystoreToWifiBlobstore(); + verify(mWifiBlobStore, times(legacyAliases.length)).put(anyString(), any(byte[].class)); + } + + /** + * Verify that if some aliases are shared between Legacy Keystore and WifiBlobstore, + * only the ones unique to Legacy Keystore are migrated. + */ + @Test + public void testKeystoreMigrationDuplicateLegacyAliases() throws Exception { + String uniqueLegacyAlias = TEST_ALIAS + "1"; + String[] blobstoreAliases = new String[]{TEST_ALIAS + "2", TEST_ALIAS + "3"}; + String[] legacyAliases = + new String[]{blobstoreAliases[0], blobstoreAliases[1], uniqueLegacyAlias}; + when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(legacyAliases); + when(mWifiBlobStore.list(anyString())).thenReturn(blobstoreAliases); + + // Expect that only the unique legacy alias is migrated to the blobstore + WifiMigration.migrateLegacyKeystoreToWifiBlobstore(); + verify(mWifiBlobStore).list(anyString()); + verify(mWifiBlobStore).put(eq(uniqueLegacyAlias), any(byte[].class)); + verifyNoMoreInteractions(mWifiBlobStore); + } +} diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig index c5bc0396de34..c1effe148e5c 100644 --- a/wifi/wifi.aconfig +++ b/wifi/wifi.aconfig @@ -17,3 +17,11 @@ flag { description: "Control the API that allows setting / reading the NetworkProviderInfo's battery charging status" bug: "305067231" } + +flag { + name: "legacy_keystore_to_wifi_blobstore_migration" + is_exported: true + namespace: "wifi" + description: "Add API to migrate all values from Legacy Keystore to the new Wifi Blobstore database" + bug: "332560152" +} |