Merge "Move util classes to common package." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 7a1add3..6b8baf8 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -99,6 +99,7 @@
"framework_graphics_flags_java_lib",
"hwui_flags_java_lib",
"libcore_exported_aconfig_flags_lib",
+ "libgui_flags_java_lib",
"power_flags_lib",
"sdk_sandbox_flags_lib",
"surfaceflinger_flags_java_lib",
@@ -1208,6 +1209,12 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "libgui_flags_java_lib",
+ aconfig_declarations: "libgui_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Content Capture
aconfig_declarations {
name: "android.view.contentcapture.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index cf73451..7f4871f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -401,6 +401,7 @@
],
sdk_version: "core_platform",
static_libs: [
+ "aconfig_storage_reader_java",
"android.hardware.common.fmq-V1-java",
"bouncycastle-repackaged-unbundled",
"com.android.sysprop.foldlockbehavior",
@@ -636,7 +637,6 @@
"core/java/com/android/internal/util/AsyncService.java",
"core/java/com/android/internal/util/Protocol.java",
"telephony/java/android/telephony/Annotation.java",
- ":net-utils-framework-wifi-common-srcs",
],
libs: [
"framework-annotations-lib",
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index ee03e4b..857154f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -116,7 +116,6 @@
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.ThreadLocalWorkSource;
import android.os.Trace;
import android.os.UserHandle;
@@ -179,9 +178,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
-import java.time.Instant;
-import java.time.zone.ZoneOffsetTransition;
-import java.time.zone.ZoneRules;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -193,7 +189,6 @@
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
@@ -233,13 +228,6 @@
private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
- // System properties read on some device configurations to initialize time properly and
- // perform DST transitions at the bootloader level.
- private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset";
- private static final String DST_TRANSITION_PROPERTY = "persist.sys.time.dst_transition";
- private static final String DST_OFFSET_PROPERTY = "persist.sys.time.dst_offset";
-
-
private final Intent mBackgroundIntent
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
@@ -2127,22 +2115,6 @@
// "GMT" if the ID is unrecognized). The parameter ID is used here rather than
// newZone.getId(). It will be rejected if it is invalid.
timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo);
-
- final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis());
- SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset));
-
- final ZoneRules rules = newZone.toZoneId().getRules();
- final ZoneOffsetTransition transition = rules.nextTransition(Instant.now());
- if (null != transition) {
- // Get the offset between the time after the DST transition and before.
- final long transitionOffset = TimeUnit.SECONDS.toMillis((
- transition.getOffsetAfter().getTotalSeconds()
- - transition.getOffsetBefore().getTotalSeconds()));
- // Time when the next DST transition is programmed.
- final long nextTransition = TimeUnit.SECONDS.toMillis(transition.toEpochSecond());
- SystemProperties.set(DST_TRANSITION_PROPERTY, String.valueOf(nextTransition));
- SystemProperties.set(DST_OFFSET_PROPERTY, String.valueOf(transitionOffset));
- }
}
// Clear the default time zone in the system server process. This forces the next call
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index c3fe031..d92351d 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1990,7 +1990,7 @@
}
}
if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) {
- if (!Flags.avoidIdleCheck()) {
+ if (!Flags.avoidIdleCheck() || mInjector.getBootPhase() >= PHASE_BOOT_COMPLETED) {
postCheckIdleStates(userId);
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 69c409b..d610f4c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -54862,8 +54862,6 @@
method @Deprecated public void addAction(int);
method public void addChild(android.view.View);
method public void addChild(android.view.View, int);
- method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View);
- method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void addLabeledBy(@NonNull android.view.View, int);
method public boolean canOpenPopup();
method public int describeContents();
method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String);
@@ -54892,7 +54890,6 @@
method public int getInputType();
method public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
- method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList();
method public int getLiveRegion();
method public int getMaxTextLength();
method @NonNull public java.time.Duration getMinDurationBetweenContentChanges();
@@ -54953,8 +54950,6 @@
method public boolean removeAction(android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction);
method public boolean removeChild(android.view.View);
method public boolean removeChild(android.view.View, int);
- method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View);
- method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public boolean removeLabeledBy(@NonNull android.view.View, int);
method public void setAccessibilityDataSensitive(boolean);
method public void setAccessibilityFocused(boolean);
method public void setAvailableExtraData(java.util.List<java.lang.String>);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fd0262e..7674246 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6718,6 +6718,14 @@
field public static final int STATUS_OK = 0; // 0x0
}
+ @FlaggedApi("android.media.soundtrigger.sound_trigger_generic_model_api") public static final class SoundTrigger.GenericSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
+ ctor public SoundTrigger.GenericSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int);
+ ctor public SoundTrigger.GenericSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.GenericSoundModel> CREATOR;
+ }
+
public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {
ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]);
method public int describeContents();
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 531537c..c83dd65 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -304,6 +304,12 @@
public boolean isTopActivityStyleFloating;
/**
+ * The last non-fullscreen bounds the task was launched in or resized to.
+ * @hide
+ */
+ public Rect lastNonFullscreenBounds;
+
+ /**
* The URI of the intent that generated the top-most activity opened using a URL.
* @hide
*/
@@ -450,6 +456,7 @@
&& Objects.equals(topActivity, that.topActivity)
&& isTopActivityTransparent == that.isTopActivityTransparent
&& isTopActivityStyleFloating == that.isTopActivityStyleFloating
+ && lastNonFullscreenBounds == this.lastNonFullscreenBounds
&& Objects.equals(capturedLink, that.capturedLink)
&& capturedLinkTimestamp == that.capturedLinkTimestamp
&& appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo);
@@ -522,6 +529,7 @@
displayAreaFeatureId = source.readInt();
isTopActivityTransparent = source.readBoolean();
isTopActivityStyleFloating = source.readBoolean();
+ lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR);
capturedLink = source.readTypedObject(Uri.CREATOR);
capturedLinkTimestamp = source.readLong();
appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
@@ -572,6 +580,7 @@
dest.writeInt(displayAreaFeatureId);
dest.writeBoolean(isTopActivityTransparent);
dest.writeBoolean(isTopActivityStyleFloating);
+ dest.writeTypedObject(lastNonFullscreenBounds, flags);
dest.writeTypedObject(capturedLink, flags);
dest.writeLong(capturedLinkTimestamp);
dest.writeTypedObject(appCompatTaskInfo, flags);
@@ -612,6 +621,7 @@
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " isTopActivityTransparent=" + isTopActivityTransparent
+ " isTopActivityStyleFloating=" + isTopActivityStyleFloating
+ + " lastNonFullscreenBounds=" + lastNonFullscreenBounds
+ " capturedLink=" + capturedLink
+ " capturedLinkTimestamp=" + capturedLinkTimestamp
+ " appCompatTaskInfo=" + appCompatTaskInfo
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 55278f6..19de793 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -117,3 +117,11 @@
description: "Enable high resolution scroll"
bug: "335160780"
}
+
+flag {
+ name: "camera_multiple_input_streams"
+ is_exported: true
+ namespace: "virtual_devices"
+ description: "Expose multiple surface for the virtual camera owner for different stream resolution"
+ bug: "341083465"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index ce241c1..88fbbdd 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -190,6 +190,18 @@
}
}
+flag {
+ name: "cache_user_serial_number_read_only"
+ namespace: "multiuser"
+ description: "Optimise user serial number retrieval. Read only flag, so that it can be used before the flags are initialized."
+ bug: "353134536"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
+
# 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/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 6113932..e8d7e1e 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -600,7 +600,7 @@
*
* <p>
* This sensor must be able to detect and report an on-body to off-body
- * transition within 1 second of the device being removed from the body,
+ * transition within 3 seconds of the device being removed from the body,
* and must be able to detect and report an off-body to on-body transition
* within 5 seconds of the device being put back onto the body.
* </p>
diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java
index c0102bf..da959af 100644
--- a/core/java/android/hardware/input/VirtualKeyEvent.java
+++ b/core/java/android/hardware/input/VirtualKeyEvent.java
@@ -286,6 +286,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualKeyEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualMouseButtonEvent.java b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
index fc42b15..333c3c7 100644
--- a/core/java/android/hardware/input/VirtualMouseButtonEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
@@ -197,6 +197,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualMouseButtonEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
index 2a42cfc..86d759d 100644
--- a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
@@ -135,6 +135,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualMouseRelativeEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualMouseScrollEvent.java b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
index c89c188..a4958c7 100644
--- a/core/java/android/hardware/input/VirtualMouseScrollEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
@@ -146,6 +146,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualMouseScrollEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
index 8c98abd..033b1c1 100644
--- a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
+++ b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
@@ -129,6 +129,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualRotaryEncoderScrollEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualStylusButtonEvent.java b/core/java/android/hardware/input/VirtualStylusButtonEvent.java
index 97a4cd0..8fcf561b 100644
--- a/core/java/android/hardware/input/VirtualStylusButtonEvent.java
+++ b/core/java/android/hardware/input/VirtualStylusButtonEvent.java
@@ -187,6 +187,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualStylusButtonEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualStylusMotionEvent.java b/core/java/android/hardware/input/VirtualStylusMotionEvent.java
index 2ab76ae..0ac6f3a 100644
--- a/core/java/android/hardware/input/VirtualStylusMotionEvent.java
+++ b/core/java/android/hardware/input/VirtualStylusMotionEvent.java
@@ -377,6 +377,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualStylusMotionEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java
index 7936dfe..0cccd25 100644
--- a/core/java/android/hardware/input/VirtualTouchEvent.java
+++ b/core/java/android/hardware/input/VirtualTouchEvent.java
@@ -354,6 +354,10 @@
* obtained from {@link SystemClock#uptimeMillis()} (with nanosecond precision instead of
* millisecond), but can be different depending on the use case.
* This field is optional and can be omitted.
+ * <p>
+ * If this field is unset, then the time at which this event is sent to the framework would
+ * be considered as the event time (even though
+ * {@link VirtualTouchEvent#getEventTimeNanos()}) would return {@code 0L}).
*
* @return this builder, to allow for chaining of calls
* @see InputEvent#getEventTime()
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 0cd2800..b4ad050 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -75,3 +75,13 @@
description: "Enables a developer overlay that displays raw touchpad input data and gesture recognition status in real-time."
bug: "286551975"
}
+
+flag {
+ namespace: "input_native"
+ name: "keyboard_layout_manager_multi_user_ime_setup"
+ description: "Update KeyboardLayoutManager to work correctly with multi-user IME setup"
+ bug: "354333072"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index bfff4db..e33a5c9 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -29,6 +29,7 @@
import static java.util.Objects.requireNonNull;
import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -874,10 +875,9 @@
/*****************************************************************************
* A GenericSoundModel is a specialized {@link SoundModel} for non-voice sound
* patterns.
- *
- * @hide
****************************************************************************/
- public static class GenericSoundModel extends SoundModel implements Parcelable {
+ @FlaggedApi(android.media.soundtrigger.Flags.FLAG_SOUND_TRIGGER_GENERIC_MODEL_API)
+ public static final class GenericSoundModel extends SoundModel implements Parcelable {
public static final @android.annotation.NonNull Parcelable.Creator<GenericSoundModel> CREATOR
= new Parcelable.Creator<GenericSoundModel>() {
@@ -890,12 +890,27 @@
}
};
+ /**
+ * Constructor for {@link GenericSoundModel} with version.
+ *
+ * @param uuid Unique identifier for this sound model.
+ * @param vendorUuid Unique vendor identifier for this sound model.
+ * @param data Opaque data for this sound model.
+ * @param version Vendor-specific version number of this sound model.
+ */
public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
@Nullable byte[] data, int version) {
- super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version);
+ super(uuid, Objects.requireNonNull(vendorUuid, "vendorUuid cannot be null"),
+ TYPE_GENERIC_SOUND, data, version);
}
- @UnsupportedAppUsage
+ /**
+ * Constructor for {@link GenericSoundModel} without version. The version is set to -1.
+ *
+ * @param uuid Unique identifier for this sound model.
+ * @param vendorUuid Unique vendor identifier for this sound model.
+ * @param data Opaque data for this sound model.
+ */
public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
@Nullable byte[] data) {
this(uuid, vendorUuid, data, -1);
@@ -919,7 +934,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(getUuid().toString());
if (getVendorUuid() == null) {
dest.writeInt(-1);
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 2447ff9..000a537 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -66,6 +66,7 @@
POWER_COMPONENT_WAKELOCK,
POWER_COMPONENT_MEMORY,
POWER_COMPONENT_PHONE,
+ POWER_COMPONENT_AMBIENT_DISPLAY,
POWER_COMPONENT_IDLE,
POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
})
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b2f333a..cdffea4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2491,7 +2491,7 @@
public static final int SCREEN_BRIGHTNESS_LIGHT = 3;
public static final int SCREEN_BRIGHTNESS_BRIGHT = 4;
- static final String[] SCREEN_BRIGHTNESS_NAMES = {
+ public static final String[] SCREEN_BRIGHTNESS_NAMES = {
"dark", "dim", "medium", "light", "bright"
};
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 44edf29..475984e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -345,7 +345,10 @@
@FlaggedApi(FLAG_VENDOR_VIBRATION_EFFECTS)
@RequiresPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)
public static VibrationEffect createVendorEffect(@NonNull PersistableBundle effect) {
- return new VendorEffect(effect, VendorEffect.DEFAULT_STRENGTH, VendorEffect.DEFAULT_SCALE);
+ VibrationEffect vendorEffect = new VendorEffect(effect, VendorEffect.DEFAULT_STRENGTH,
+ VendorEffect.DEFAULT_SCALE);
+ vendorEffect.validate();
+ return vendorEffect;
}
/**
@@ -1204,9 +1207,7 @@
}
return mEffectStrength == other.mEffectStrength
&& (Float.compare(mLinearScale, other.mLinearScale) == 0)
- // Make sure it calls unparcel for both before calling BaseBundle.kindofEquals.
- && mVendorData.size() == other.mVendorData.size()
- && BaseBundle.kindofEquals(mVendorData, other.mVendorData);
+ && isPersistableBundleEquals(mVendorData, other.mVendorData);
}
@Override
@@ -1243,6 +1244,55 @@
out.writeFloat(mLinearScale);
}
+ /**
+ * Compares two {@link PersistableBundle} objects are equals.
+ */
+ private static boolean isPersistableBundleEquals(
+ PersistableBundle first, PersistableBundle second) {
+ if (first == second) {
+ return true;
+ }
+ if (first == null || second == null || first.size() != second.size()) {
+ return false;
+ }
+ for (String key : first.keySet()) {
+ if (!isPersistableBundleSupportedValueEquals(first.get(key), second.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compares two values which type is supported by {@link PersistableBundle}.
+ *
+ * <p>If the type isn't supported. The equality is done by {@link Object#equals(Object)}.
+ */
+ private static boolean isPersistableBundleSupportedValueEquals(
+ Object first, Object second) {
+ if (first == second) {
+ return true;
+ } else if (first == null || second == null
+ || !first.getClass().equals(second.getClass())) {
+ return false;
+ } else if (first instanceof PersistableBundle) {
+ return isPersistableBundleEquals(
+ (PersistableBundle) first, (PersistableBundle) second);
+ } else if (first instanceof int[]) {
+ return Arrays.equals((int[]) first, (int[]) second);
+ } else if (first instanceof long[]) {
+ return Arrays.equals((long[]) first, (long[]) second);
+ } else if (first instanceof double[]) {
+ return Arrays.equals((double[]) first, (double[]) second);
+ } else if (first instanceof boolean[]) {
+ return Arrays.equals((boolean[]) first, (boolean[]) second);
+ } else if (first instanceof String[]) {
+ return Arrays.equals((String[]) first, (String[]) second);
+ } else {
+ return Objects.equals(first, second);
+ }
+ }
+
@NonNull
public static final Creator<VendorEffect> CREATOR =
new Creator<VendorEffect>() {
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 71c83f2..161cce0 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -497,7 +497,27 @@
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
public void vibrate(@NonNull VibrationEffect vibe, @NonNull VibrationAttributes attributes) {
- vibrate(Process.myUid(), mPackageName, vibe, null, attributes);
+ vibrate(vibe, attributes, null);
+ }
+
+ /**
+ * Vibrate with a given effect.
+ *
+ * <p>The app should be in the foreground for the vibration to happen. Background apps should
+ * specify a ringtone, notification or alarm usage in order to vibrate.</p>
+ *
+ * @param vibe {@link VibrationEffect} describing the vibration to be performed.
+ * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
+ * specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
+ * {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
+ * incoming calls.
+ * @param reason the reason for this vibration, used for debugging purposes.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE)
+ public void vibrate(@NonNull VibrationEffect vibe,
+ @NonNull VibrationAttributes attributes, String reason) {
+ vibrate(Process.myUid(), mPackageName, vibe, reason, attributes);
}
/**
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index f4e2a7e..62b3682 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -64,3 +64,13 @@
purpose: PURPOSE_FEATURE
}
}
+
+flag {
+ namespace: "haptics"
+ name: "throttle_vibration_params_requests"
+ description: "Control the frequency of vibration params requests to prevent overloading the vendor service"
+ bug: "355320860"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 708c196..192afb1 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -28,6 +28,7 @@
import android.annotation.SystemApi;
import android.annotation.UserHandleAware;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -1624,6 +1625,19 @@
"is_call_log_phone_account_migration_pending";
/**
+ * The default maximum number of call log entries stored in the call log provider for each
+ * {@link PhoneAccountHandle}.
+ */
+ private static final int DEFAULT_MAX_CALL_LOG_SIZE = 500;
+
+ /**
+ * Expected component name of Telephony phone accounts.
+ */
+ private static final ComponentName TELEPHONY_COMPONENT_NAME =
+ new ComponentName("com.android.phone",
+ "com.android.services.telephony.TelephonyConnectionService");
+
+ /**
* Adds a call to the call log.
*
* @param ci the CallerInfo object to get the target contact from. Can be null
@@ -2084,25 +2098,35 @@
}
int numDeleted;
- if (values.containsKey(PHONE_ACCOUNT_ID)
- && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_ID))
- && values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME)
- && !TextUtils.isEmpty(values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME))) {
+ final String phoneAccountId =
+ values.containsKey(PHONE_ACCOUNT_ID)
+ ? values.getAsString(PHONE_ACCOUNT_ID) : null;
+ final String phoneAccountComponentName =
+ values.containsKey(PHONE_ACCOUNT_COMPONENT_NAME)
+ ? values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME) : null;
+ int maxCallLogSize = DEFAULT_MAX_CALL_LOG_SIZE;
+ if (!TextUtils.isEmpty(phoneAccountId)
+ && !TextUtils.isEmpty(phoneAccountComponentName)) {
+ if (android.provider.Flags.allowConfigMaximumCallLogEntriesPerSim()
+ && TELEPHONY_COMPONENT_NAME
+ .flattenToString().equals(phoneAccountComponentName)) {
+ maxCallLogSize = context.getResources().getInteger(
+ com.android.internal.R.integer.config_maximumCallLogEntriesPerSim);
+ }
// Only purge entries for the same phone account.
numDeleted = resolver.delete(uri, "_id IN "
+ "(SELECT _id FROM calls"
+ " WHERE " + PHONE_ACCOUNT_COMPONENT_NAME + " = ?"
+ " AND " + PHONE_ACCOUNT_ID + " = ?"
+ " ORDER BY " + DEFAULT_SORT_ORDER
- + " LIMIT -1 OFFSET 500)", new String[] {
- values.getAsString(PHONE_ACCOUNT_COMPONENT_NAME),
- values.getAsString(PHONE_ACCOUNT_ID)
- });
+ + " LIMIT -1 OFFSET " + maxCallLogSize + ")",
+ new String[] { phoneAccountComponentName, phoneAccountId }
+ );
} else {
// No valid phone account specified, so default to the old behavior.
numDeleted = resolver.delete(uri, "_id IN "
+ "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
- + " LIMIT -1 OFFSET 500)", null);
+ + " LIMIT -1 OFFSET " + maxCallLogSize + ")", null);
}
Log.i(LOG_TAG, "addEntry: cleaned up " + numDeleted + " old entries");
diff --git a/core/java/android/provider/flags.aconfig b/core/java/android/provider/flags.aconfig
index ff98fc4..53d0c62 100644
--- a/core/java/android/provider/flags.aconfig
+++ b/core/java/android/provider/flags.aconfig
@@ -30,4 +30,16 @@
namespace: "backstage_power"
description: "Add a new settings page for the RUN_BACKUP_JOBS permission."
bug: "320563660"
-}
\ No newline at end of file
+}
+
+# OWNER = tgunn TARGET=25Q1
+flag {
+ name: "allow_config_maximum_call_log_entries_per_sim"
+ is_exported: true
+ namespace: "telecom"
+ description: "Allow partners to modify the maximum number of call log size for each sim card."
+ bug: "352235494"
+ metadata {
+ purpose: PURPOSE_FEATURE
+ }
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index fbeab84..d454716 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -266,9 +266,9 @@
private boolean mDozing;
private boolean mWindowless;
private boolean mPreviewMode;
- private int mDozeScreenState = Display.STATE_UNKNOWN;
- private @Display.StateReason int mDozeScreenStateReason = Display.STATE_REASON_UNKNOWN;
- private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+ private volatile int mDozeScreenState = Display.STATE_UNKNOWN;
+ private volatile @Display.StateReason int mDozeScreenStateReason = Display.STATE_REASON_UNKNOWN;
+ private volatile int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
private boolean mDebug = false;
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 620eef6..76f6363 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -39,7 +39,9 @@
@UnsupportedAppUsage
boolean isDreamingOrInPreview();
boolean canStartDreaming(boolean isScreenOn);
+ /** @deprecated Please use finishSelfOneway instead. */
void finishSelf(in IBinder token, boolean immediate);
+ /** @deprecated Please use startDozingOneway instead. */
void startDozing(in IBinder token, int screenState, int reason, int screenBrightness);
void stopDozing(in IBinder token);
void forceAmbientDisplayEnabled(boolean enabled);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 46b222b..66d08f9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -404,8 +404,7 @@
}
private void prepareToDraw() {
- if (mDisplayState == Display.STATE_DOZE
- || mDisplayState == Display.STATE_DOZE_SUSPEND) {
+ if (mDisplayState == Display.STATE_DOZE) {
try {
mSession.pokeDrawLock(mWindow);
} catch (RemoteException e) {
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 4281da1..5ac0c50 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1689,6 +1689,10 @@
public final void onCarrierRoamingNtnModeChanged(boolean active) {
// not supported on the deprecated interface - Use TelephonyCallback instead
}
+
+ public final void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {
+ // not supported on the deprecated interface - Use TelephonyCallback instead
+ }
}
private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index b8b84d9..c360e64 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -653,6 +653,27 @@
public static final int EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED = 42;
/**
+ * Event for listening to changes in carrier roaming non-terrestrial network eligibility.
+ *
+ * @see CarrierRoamingNtnModeListener
+ *
+ * Device is eligible for satellite communication if all the following conditions are met:
+ * <ul>
+ * <li>Any subscription on the device supports P2P satellite messaging which is defined by
+ * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+ * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to
+ * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li>
+ * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi,
+ * and the hysteresis timer defined by {@link CarrierConfigManager
+ * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired.
+ * </li>
+ * </ul>
+ *
+ * @hide
+ */
+ public static final int EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED = 43;
+
+ /**
* @hide
*/
@IntDef(prefix = {"EVENT_"}, value = {
@@ -697,7 +718,8 @@
EVENT_MEDIA_QUALITY_STATUS_CHANGED,
EVENT_EMERGENCY_CALLBACK_MODE_CHANGED,
EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED,
- EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED
+ EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED,
+ EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TelephonyEvent {
@@ -1711,6 +1733,23 @@
* {code false} otherwise.
*/
void onCarrierRoamingNtnModeChanged(boolean active);
+
+ /**
+ * Callback invoked when carrier roaming non-terrestrial network eligibility changes.
+ *
+ * @param eligible {@code true} when the device is eligible for satellite
+ * communication if all the following conditions are met:
+ * <ul>
+ * <li>Any subscription on the device supports P2P satellite messaging which is defined by
+ * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+ * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to
+ * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li>
+ * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi,
+ * and the hysteresis timer defined by {@link CarrierConfigManager
+ * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired. </li>
+ * </ul>
+ */
+ default void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {}
}
/**
@@ -2125,5 +2164,16 @@
Binder.withCleanCallingIdentity(
() -> mExecutor.execute(() -> listener.onCarrierRoamingNtnModeChanged(active)));
}
+
+ public void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {
+ if (!Flags.carrierRoamingNbIotNtn()) return;
+
+ CarrierRoamingNtnModeListener listener =
+ (CarrierRoamingNtnModeListener) mTelephonyCallbackWeakRef.get();
+ if (listener == null) return;
+
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> listener.onCarrierRoamingNtnEligibleStateChanged(eligible)));
+ }
}
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 6160fdb..10f03c1 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -1091,6 +1091,34 @@
}
/**
+ * Notify external listeners that device eligibility to connect to carrier roaming
+ * non-terrestrial network changed.
+ *
+ * @param subId subscription ID.
+ * @param eligible {@code true} when the device is eligible for satellite
+ * communication if all the following conditions are met:
+ * <ul>
+ * <li>Any subscription supports P2P satellite messaging which is defined by
+ * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+ * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to
+ * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li>
+ * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi,
+ * and the hysteresis timer defined by {@link CarrierConfigManager
+ * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired. </li>
+ * </ul>
+ *
+ * @hide
+ */
+ public void notifyCarrierRoamingNtnEligibleStateChanged(int subId, boolean eligible) {
+ try {
+ sRegistry.notifyCarrierRoamingNtnEligibleStateChanged(subId, eligible);
+ } catch (RemoteException ex) {
+ // system server crash
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Processes potential event changes from the provided {@link TelephonyCallback}.
*
* @param telephonyCallback callback for monitoring callback changes to the telephony state.
@@ -1246,6 +1274,11 @@
if (telephonyCallback instanceof TelephonyCallback.CarrierRoamingNtnModeListener) {
eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED);
}
+
+ if (telephonyCallback instanceof TelephonyCallback.CarrierRoamingNtnModeListener) {
+ eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED);
+ }
+
return eventList;
}
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 9db8aa1..4fdcecc 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -30,6 +30,8 @@
import android.graphics.text.LineBreakConfig;
import android.text.style.ParagraphStyle;
+import com.android.text.flags.Flags;
+
/**
* A BoringLayout is a very simple Layout implementation for text that
* fits on a single line and is all left-to-right characters.
@@ -589,7 +591,7 @@
fm.reset();
}
- if (ClientFlags.fixLineHeightForLocale()) {
+ if (Flags.fixLineHeightForLocale()) {
if (minimumFontMetrics != null) {
fm.set(minimumFontMetrics);
// Because the font metrics is provided by public APIs, adjust the top/bottom with
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index b07534f..5d84d17 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -68,11 +68,4 @@
public static boolean fixMisalignedContextMenu() {
return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU);
}
-
- /**
- * @see Flags#clearFontVariationSettings()
- */
- public static boolean clearFontVariationSettings() {
- return TextFlags.isFeatureEnabled(Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS);
- }
}
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index 161a79b..896e087 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -42,6 +42,8 @@
import android.text.style.ReplacementSpan;
import android.util.Pools.SynchronizedPool;
+import com.android.text.flags.Flags;
+
import java.util.Arrays;
/**
@@ -199,7 +201,7 @@
* @hide
*/
public @Layout.Direction int getParagraphDir() {
- if (ClientFlags.icuBidiMigration()) {
+ if (Flags.icuBidiMigration()) {
if (mBidi == null) {
return Layout.DIR_LEFT_TO_RIGHT;
}
@@ -217,7 +219,7 @@
*/
public Directions getDirections(@IntRange(from = 0) int start, // inclusive
@IntRange(from = 0) int end) { // exclusive
- if (ClientFlags.icuBidiMigration()) {
+ if (Flags.icuBidiMigration()) {
// Easy case: mBidi == null means the text is all LTR and no bidi suppot is needed.
if (mBidi == null) {
return Layout.DIRS_ALL_LEFT_TO_RIGHT;
@@ -679,7 +681,7 @@
}
}
- if (ClientFlags.icuBidiMigration()) {
+ if (Flags.icuBidiMigration()) {
if ((textDir == TextDirectionHeuristics.LTR
|| textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
|| textDir == TextDirectionHeuristics.ANYRTL_LTR)
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 95460a3..cb49850 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -41,6 +41,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import com.android.text.flags.Flags;
import java.util.Arrays;
@@ -803,7 +804,7 @@
final int defaultAscent;
final int defaultDescent;
int defaultBottom;
- if (ClientFlags.fixLineHeightForLocale() && b.mMinimumFontMetrics != null) {
+ if (Flags.fixLineHeightForLocale() && b.mMinimumFontMetrics != null) {
defaultTop = (int) Math.floor(b.mMinimumFontMetrics.top);
defaultAscent = Math.round(b.mMinimumFontMetrics.ascent);
defaultDescent = Math.round(b.mMinimumFontMetrics.descent);
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 4dca284..9e02460 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -61,7 +61,6 @@
Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
Flags.FLAG_ICU_BIDI_MIGRATION,
Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU,
- Flags.FLAG_CLEAR_FONT_VARIATION_SETTINGS,
};
/**
@@ -76,7 +75,6 @@
Flags.fixLineHeightForLocale(),
Flags.icuBidiMigration(),
Flags.fixMisalignedContextMenu(),
- Flags.clearFontVariationSettings(),
};
/**
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 5c10db1..b796e0b 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -104,12 +104,23 @@
*/
public static final int FLAG_ANIMATE_RESIZING = 1 << 3;
+ /**
+ * Controls whether the {@link WindowInsets.Type#captionBar()} insets provided by this source
+ * should always be forcibly consumed. Unlike with {@link #FLAG_FORCE_CONSUMING}, when this
+ * flag is used the caption bar will be consumed even when the bar is requested to be visible.
+ *
+ * Note: this flag does not take effect when the window applies
+ * {@link WindowInsetsController.Appearance#APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND}.
+ */
+ public static final int FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR = 1 << 4;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = "FLAG_", value = {
FLAG_SUPPRESS_SCRIM,
FLAG_INSETS_ROUNDED_CORNER,
FLAG_FORCE_CONSUMING,
FLAG_ANIMATE_RESIZING,
+ FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR
})
public @interface Flags {}
@@ -555,6 +566,9 @@
if ((flags & FLAG_ANIMATE_RESIZING) != 0) {
joiner.add("ANIMATE_RESIZING");
}
+ if ((flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) {
+ joiner.add("FORCE_CONSUMING_OPAQUE_CAPTION_BAR");
+ }
return joiner.toString();
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index bbd9acf..6b4340a 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.util.SequenceUtils.getInitSeq;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
import static android.view.InsetsStateProto.DISPLAY_FRAME;
@@ -54,6 +55,7 @@
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.Objects;
@@ -131,18 +133,25 @@
final Rect relativeFrame = new Rect(frame);
final Rect relativeFrameMax = new Rect(frame);
@InsetsType int forceConsumingTypes = 0;
+ boolean forceConsumingOpaqueCaptionBar = false;
@InsetsType int suppressScrimTypes = 0;
final Rect[][] typeBoundingRectsMap = new Rect[Type.SIZE][];
final Rect[][] typeMaxBoundingRectsMap = new Rect[Type.SIZE][];
for (int i = mSources.size() - 1; i >= 0; i--) {
final InsetsSource source = mSources.valueAt(i);
final @InsetsType int type = source.getType();
+ final @InsetsSource.Flags int flags = source.getFlags();
- if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
+ if ((flags & InsetsSource.FLAG_FORCE_CONSUMING) != 0) {
forceConsumingTypes |= type;
}
- if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
+ if (Flags.enableCaptionCompatInsetForceConsumptionAlways()
+ && (flags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0) {
+ forceConsumingOpaqueCaptionBar = true;
+ }
+
+ if ((flags & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) {
suppressScrimTypes |= type;
}
@@ -177,7 +186,8 @@
}
return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
- forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame),
+ forceConsumingTypes, forceConsumingOpaqueCaptionBar, suppressScrimTypes,
+ calculateRelativeCutout(frame),
calculateRelativeRoundedCorners(frame),
calculateRelativePrivacyIndicatorBounds(frame),
calculateRelativeDisplayShape(frame),
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 634469d..cf329d3 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -164,6 +164,9 @@
float width, float height, float vecX, float vecY,
float maxStretchAmountX, float maxStretchAmountY, float childRelativeLeft,
float childRelativeTop, float childRelativeRight, float childRelativeBottom);
+ private static native void nativeSetEdgeExtensionEffect(long transactionObj, long nativeObj,
+ boolean leftEdge, boolean rightEdge,
+ boolean topEdge, boolean bottomEdge);
private static native void nativeSetTrustedOverlay(long transactionObj, long nativeObject,
int isTrustedOverlay);
private static native void nativeSetDropInputMode(
@@ -3513,6 +3516,19 @@
/**
* @hide
*/
+ public Transaction setEdgeExtensionEffect(SurfaceControl sc, int edge) {
+ checkPreconditions(sc);
+
+ nativeSetEdgeExtensionEffect(
+ mNativeObject, sc.mNativeObject,
+ (edge & WindowInsets.Side.LEFT) != 0, (edge & WindowInsets.Side.RIGHT) != 0,
+ (edge & WindowInsets.Side.TOP) != 0, (edge & WindowInsets.Side.BOTTOM) != 0);
+ return this;
+ }
+
+ /**
+ * @hide
+ */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O)
public Transaction setLayerStack(SurfaceControl sc, int layerStack) {
checkPreconditions(sc);
@@ -4882,4 +4898,5 @@
public static void notifyShutdown() {
nativeNotifyShutdown();
}
+
}
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 987c8c8..e3ea6b22 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -97,6 +97,7 @@
private final int mFrameHeight;
private final @InsetsType int mForceConsumingTypes;
+ private final boolean mForceConsumingOpaqueCaptionBar;
private final @InsetsType int mSuppressScrimTypes;
private final boolean mSystemWindowInsetsConsumed;
private final boolean mStableInsetsConsumed;
@@ -123,7 +124,7 @@
static {
CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
- createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null,
+ createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, false, 0, null,
null, null, null, systemBars(), false, null, null, 0, 0);
}
@@ -144,6 +145,7 @@
boolean[] typeVisibilityMap,
boolean isRound,
@InsetsType int forceConsumingTypes,
+ boolean forceConsumingOpaqueCaptionBar,
@InsetsType int suppressScrimTypes,
DisplayCutout displayCutout,
RoundedCorners roundedCorners,
@@ -166,6 +168,7 @@
mTypeVisibilityMap = typeVisibilityMap;
mIsRound = isRound;
mForceConsumingTypes = forceConsumingTypes;
+ mForceConsumingOpaqueCaptionBar = forceConsumingOpaqueCaptionBar;
mSuppressScrimTypes = suppressScrimTypes;
mCompatInsetsTypes = compatInsetsTypes;
mCompatIgnoreVisibility = compatIgnoreVisibility;
@@ -196,7 +199,9 @@
this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap,
src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
src.mTypeVisibilityMap, src.mIsRound,
- src.mForceConsumingTypes, src.mSuppressScrimTypes,
+ src.mForceConsumingTypes,
+ src.mForceConsumingOpaqueCaptionBar,
+ src.mSuppressScrimTypes,
displayCutoutCopyConstructorArgument(src),
src.mRoundedCorners,
src.mPrivacyIndicatorBounds,
@@ -257,7 +262,7 @@
/** @hide */
@UnsupportedAppUsage
public WindowInsets(Rect systemWindowInsets) {
- this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0,
+ this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, false, 0,
null, null, null, null, systemBars(), false /* compatIgnoreVisibility */,
new Rect[SIZE][], null, 0, 0);
}
@@ -675,10 +680,10 @@
return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
- null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
- mCompatInsetsTypes, mCompatIgnoreVisibility,
- mSystemWindowInsetsConsumed ? null : mTypeBoundingRectsMap,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes, null /* displayCutout */, mRoundedCorners,
+ mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes,
+ mCompatIgnoreVisibility, mSystemWindowInsetsConsumed ? null : mTypeBoundingRectsMap,
mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
mFrameWidth, mFrameHeight);
}
@@ -729,7 +734,8 @@
public WindowInsets consumeSystemWindowInsets() {
return new WindowInsets(null, null,
mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes,
// If the system window insets types contain displayCutout, we should also consume
// it.
(mCompatInsetsTypes & displayCutout()) != 0
@@ -1024,6 +1030,13 @@
/**
* @hide
*/
+ public boolean isForceConsumingOpaqueCaptionBar() {
+ return mForceConsumingOpaqueCaptionBar;
+ }
+
+ /**
+ * @hide
+ */
public @InsetsType int getSuppressScrimTypes() {
return mSuppressScrimTypes;
}
@@ -1058,6 +1071,8 @@
result.append("\n ");
result.append("forceConsumingTypes=" + Type.toString(mForceConsumingTypes));
result.append("\n ");
+ result.append("forceConsumingOpaqueCaptionBar=" + mForceConsumingOpaqueCaptionBar);
+ result.append("\n ");
result.append("suppressScrimTypes=" + Type.toString(mSuppressScrimTypes));
result.append("\n ");
result.append("compatInsetsTypes=" + Type.toString(mCompatInsetsTypes));
@@ -1180,7 +1195,8 @@
? null
: insetInsets(mTypeMaxInsetsMap, left, top, right, bottom),
mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes,
mDisplayCutoutConsumed
? null
: mDisplayCutout == null
@@ -1214,6 +1230,7 @@
return mIsRound == that.mIsRound
&& mForceConsumingTypes == that.mForceConsumingTypes
+ && mForceConsumingOpaqueCaptionBar == that.mForceConsumingOpaqueCaptionBar
&& mSuppressScrimTypes == that.mSuppressScrimTypes
&& mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
&& mStableInsetsConsumed == that.mStableInsetsConsumed
@@ -1235,9 +1252,9 @@
public int hashCode() {
return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
- mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
- mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds,
- mDisplayShape, Arrays.deepHashCode(mTypeBoundingRectsMap),
+ mForceConsumingTypes, mForceConsumingOpaqueCaptionBar, mSuppressScrimTypes,
+ mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed,
+ mPrivacyIndicatorBounds, mDisplayShape, Arrays.deepHashCode(mTypeBoundingRectsMap),
Arrays.deepHashCode(mTypeMaxBoundingRectsMap), mFrameWidth, mFrameHeight);
}
@@ -1367,6 +1384,7 @@
private boolean mIsRound;
private @InsetsType int mForceConsumingTypes;
+ private boolean mForceConsumingOpaqueCaptionBar;
private @InsetsType int mSuppressScrimTypes;
private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds();
@@ -1399,6 +1417,7 @@
mRoundedCorners = insets.mRoundedCorners;
mIsRound = insets.mIsRound;
mForceConsumingTypes = insets.mForceConsumingTypes;
+ mForceConsumingOpaqueCaptionBar = insets.mForceConsumingOpaqueCaptionBar;
mSuppressScrimTypes = insets.mSuppressScrimTypes;
mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
mDisplayShape = insets.mDisplayShape;
@@ -1687,6 +1706,13 @@
/** @hide */
@NonNull
+ public Builder setForceConsumingOpaqueCaptionBar(boolean forceConsumingOpaqueCaptionBar) {
+ mForceConsumingOpaqueCaptionBar = forceConsumingOpaqueCaptionBar;
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
public Builder setSuppressScrimTypes(@InsetsType int suppressScrimTypes) {
mSuppressScrimTypes = suppressScrimTypes;
return this;
@@ -1765,9 +1791,9 @@
public WindowInsets build() {
return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
- mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout,
- mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
- false /* compatIgnoreVisibility */,
+ mIsRound, mForceConsumingTypes, mForceConsumingOpaqueCaptionBar,
+ mSuppressScrimTypes, mDisplayCutout, mRoundedCorners, mPrivacyIndicatorBounds,
+ mDisplayShape, systemBars(), false /* compatIgnoreVisibility */,
mSystemInsetsConsumed ? null : mTypeBoundingRectsMap,
mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
mFrameWidth, mFrameHeight);
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index f3cde43..376e66f 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -19,6 +19,7 @@
import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_ACCESSIBILITY;
+import android.annotation.Nullable;
import android.os.Build;
import android.os.SystemClock;
import android.util.ArraySet;
@@ -48,6 +49,8 @@
private boolean mEnabled = true;
+ private final SparseArray<String> mWindowIdToEventSourceClassName = new SparseArray<>();
+
/**
* {@link AccessibilityEvent} types that are critical for the cache to stay up to date
*
@@ -273,8 +276,11 @@
clearSubTreeLocked(event.getWindowId(), event.getSourceNodeId());
} break;
- case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
+ case AccessibilityEvent.TYPE_WINDOWS_CHANGED: {
mValidWindowCacheTimeStamp = event.getEventTime();
+ if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED) {
+ mWindowIdToEventSourceClassName.remove(event.getWindowId());
+ }
if (event.getWindowChanges()
== AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) {
// Don't need to clear all cache. Unless the changes are related to
@@ -282,8 +288,15 @@
clearWindowCacheLocked();
break;
}
+ clear();
+ }
+ break;
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
mValidWindowCacheTimeStamp = event.getEventTime();
+ if (event.getContentChangeTypes() == 0 && event.getClassName() != null) {
+ mWindowIdToEventSourceClassName.put(event.getWindowId(),
+ event.getClassName().toString());
+ }
clear();
} break;
}
@@ -907,6 +920,12 @@
}
}
+ /** Returns the source class associated with the window with the given id. */
+ @Nullable
+ public String getEventSourceClassName(int windowId) {
+ return mWindowIdToEventSourceClassName.get(windowId);
+ }
+
// Layer of indirection included to break dependency chain for testing
public static class AccessibilityNodeRefresher {
/** Refresh the given AccessibilityNodeInfo object. */
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 90cfcb1..a5ba294 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -982,7 +982,6 @@
private long mParentNodeId = UNDEFINED_NODE_ID;
private long mLabelForId = UNDEFINED_NODE_ID;
private long mLabeledById = UNDEFINED_NODE_ID;
- private LongArray mLabeledByIds;
private long mTraversalBefore = UNDEFINED_NODE_ID;
private long mTraversalAfter = UNDEFINED_NODE_ID;
@@ -3600,131 +3599,6 @@
}
/**
- * Adds the view which serves as the label of the view represented by
- * this info for accessibility purposes. When more than one labels are
- * added, the content from each label is combined in the order that
- * they are added.
- * <p>
- * If visible text can be used to describe or give meaning to this UI,
- * this method is preferred. For example, a TextView before an EditText
- * in the UI usually specifies what information is contained in the
- * EditText. Hence, the EditText is labelled by the TextView.
- * </p>
- *
- * @param label A view that labels this node's source.
- */
- @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
- public void addLabeledBy(@NonNull View label) {
- addLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
- }
-
- /**
- * Adds the view which serves as the label of the view represented by
- * this info for accessibility purposes. If <code>virtualDescendantId</code>
- * is {@link View#NO_ID} the root is set as the label. When more than one
- * labels are added, the content from each label is combined in the order
- * that they are added.
- * <p>
- * A virtual descendant is an imaginary View that is reported as a part of the view
- * hierarchy for accessibility purposes. This enables custom views that draw complex
- * content to report themselves as a tree of virtual views, thus conveying their
- * logical structure.
- * </p>
- * <p>
- * If visible text can be used to describe or give meaning to this UI,
- * this method is preferred. For example, a TextView before an EditText
- * in the UI usually specifies what information is contained in the
- * EditText. Hence, the EditText is labelled by the TextView.
- * </p>
- * <p>
- * <strong>Note:</strong> Cannot be called from an
- * {@link android.accessibilityservice.AccessibilityService}.
- * This class is made immutable before being delivered to an AccessibilityService.
- * </p>
- *
- * @param root A root whose virtual descendant labels this node's source.
- * @param virtualDescendantId The id of the virtual descendant.
- */
- @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
- public void addLabeledBy(@NonNull View root, int virtualDescendantId) {
- enforceNotSealed();
- Preconditions.checkNotNull(root, "%s must not be null", root);
- if (mLabeledByIds == null) {
- mLabeledByIds = new LongArray();
- }
- mLabeledById = makeNodeId(root.getAccessibilityViewId(), virtualDescendantId);
- mLabeledByIds.add(mLabeledById);
- }
-
- /**
- * Gets the list of node infos which serve as the labels of the view represented by
- * this info for accessibility purposes.
- *
- * @return The list of labels in the order that they were added.
- */
- @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
- public @NonNull List<AccessibilityNodeInfo> getLabeledByList() {
- enforceSealed();
- List<AccessibilityNodeInfo> labels = new ArrayList<>();
- if (mLabeledByIds == null) {
- return labels;
- }
- for (int i = 0; i < mLabeledByIds.size(); i++) {
- labels.add(getNodeForAccessibilityId(mConnectionId, mWindowId, mLabeledByIds.get(i)));
- }
- return labels;
- }
-
- /**
- * Removes a label. If the label was not previously added to the node,
- * calling this method has no effect.
- * <p>
- * <strong>Note:</strong> Cannot be called from an
- * {@link android.accessibilityservice.AccessibilityService}.
- * This class is made immutable before being delivered to an AccessibilityService.
- * </p>
- *
- * @param label The node which serves as this node's label.
- * @return true if the label was present
- * @see #addLabeledBy(View)
- */
- @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
- public boolean removeLabeledBy(@NonNull View label) {
- return removeLabeledBy(label, AccessibilityNodeProvider.HOST_VIEW_ID);
- }
-
- /**
- * Removes a virtual label which is a descendant of the given
- * <code>root</code>. If the label was not previously added to the node,
- * calling this method has no effect.
- *
- * @param root The root of the virtual subtree.
- * @param virtualDescendantId The id of the virtual node which serves as this node's label.
- * @return true if the label was present
- * @see #addLabeledBy(View, int)
- */
- @FlaggedApi(Flags.FLAG_SUPPORT_MULTIPLE_LABELEDBY)
- public boolean removeLabeledBy(@NonNull View root, int virtualDescendantId) {
- enforceNotSealed();
- final LongArray labeledByIds = mLabeledByIds;
- if (labeledByIds == null) {
- return false;
- }
- final int rootAccessibilityViewId =
- (root != null) ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
- final long labeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
- if (mLabeledById == labeledById) {
- mLabeledById = UNDEFINED_NODE_ID;
- }
- final int index = labeledByIds.indexOf(labeledById);
- if (index < 0) {
- return false;
- }
- labeledByIds.remove(index);
- return true;
- }
-
- /**
* Sets the view which serves as the label of the view represented by
* this info for accessibility purposes.
*
@@ -3757,17 +3631,7 @@
enforceNotSealed();
final int rootAccessibilityViewId = (root != null)
? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID;
- if (Flags.supportMultipleLabeledby()) {
- if (mLabeledByIds == null) {
- mLabeledByIds = new LongArray();
- } else {
- mLabeledByIds.clear();
- }
- }
mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
- if (Flags.supportMultipleLabeledby()) {
- mLabeledByIds.add(mLabeledById);
- }
}
/**
@@ -4378,12 +4242,6 @@
fieldIndex++;
if (mLabeledById != DEFAULT.mLabeledById) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
- if (Flags.supportMultipleLabeledby()) {
- if (!LongArray.elementsEqual(mLabeledByIds, DEFAULT.mLabeledByIds)) {
- nonDefaultFields |= bitAt(fieldIndex);
- }
- fieldIndex++;
- }
if (mTraversalBefore != DEFAULT.mTraversalBefore) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
@@ -4525,20 +4383,6 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mParentNodeId);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabelForId);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mLabeledById);
- if (Flags.supportMultipleLabeledby()) {
- if (isBitSet(nonDefaultFields, fieldIndex++)) {
- final LongArray labeledByIds = mLabeledByIds;
- if (labeledByIds == null) {
- parcel.writeInt(0);
- } else {
- final int labeledByIdsSize = labeledByIds.size();
- parcel.writeInt(labeledByIdsSize);
- for (int i = 0; i < labeledByIdsSize; i++) {
- parcel.writeLong(labeledByIds.get(i));
- }
- }
- }
- }
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
if (isBitSet(nonDefaultFields, fieldIndex++)) {
@@ -4706,9 +4550,6 @@
mParentNodeId = other.mParentNodeId;
mLabelForId = other.mLabelForId;
mLabeledById = other.mLabeledById;
- if (Flags.supportMultipleLabeledby()) {
- mLabeledByIds = other.mLabeledByIds;
- }
mTraversalBefore = other.mTraversalBefore;
mTraversalAfter = other.mTraversalAfter;
mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges;
@@ -4815,20 +4656,6 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) mParentNodeId = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mLabelForId = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mLabeledById = parcel.readLong();
- if (Flags.supportMultipleLabeledby()) {
- if (isBitSet(nonDefaultFields, fieldIndex++)) {
- final int labeledByIdsSize = parcel.readInt();
- if (labeledByIdsSize <= 0) {
- mLabeledByIds = null;
- } else {
- mLabeledByIds = new LongArray(labeledByIdsSize);
- for (int i = 0; i < labeledByIdsSize; i++) {
- final long labeledById = parcel.readLong();
- mLabeledByIds.add(labeledById);
- }
- }
- }
- }
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) {
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index 09306c7..2af935d 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -28,6 +28,7 @@
import android.os.SystemProperties;
import android.util.AttributeSet;
import android.util.TypedValue;
+import android.view.WindowInsets;
import dalvik.system.CloseGuard;
@@ -881,12 +882,13 @@
}
/**
- * @return if a window animation has outsets applied to it.
+ * @return the edges to which outsets can be applied to
*
* @hide
*/
- public boolean hasExtension() {
- return false;
+ @WindowInsets.Side.InsetsSide
+ public int getExtensionEdges() {
+ return 0x0;
}
/**
diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java
index 5aaa994..bbdc9d0 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -21,6 +21,7 @@
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
+import android.view.WindowInsets;
import java.util.ArrayList;
import java.util.List;
@@ -540,12 +541,12 @@
/** @hide */
@Override
- public boolean hasExtension() {
+ @WindowInsets.Side.InsetsSide
+ public int getExtensionEdges() {
+ int edge = 0x0;
for (Animation animation : mAnimations) {
- if (animation.hasExtension()) {
- return true;
- }
+ edge |= animation.getExtensionEdges();
}
- return false;
+ return edge;
}
}
diff --git a/core/java/android/view/animation/ExtendAnimation.java b/core/java/android/view/animation/ExtendAnimation.java
index 210eb8a..ed047c7 100644
--- a/core/java/android/view/animation/ExtendAnimation.java
+++ b/core/java/android/view/animation/ExtendAnimation.java
@@ -20,6 +20,7 @@
import android.content.res.TypedArray;
import android.graphics.Insets;
import android.util.AttributeSet;
+import android.view.WindowInsets;
/**
* An animation that controls the outset of an object.
@@ -50,6 +51,8 @@
private float mToRightValue;
private float mToBottomValue;
+ private int mExtensionEdges = 0x0;
+
/**
* Constructor used when an ExtendAnimation is loaded from a resource.
*
@@ -151,9 +154,22 @@
/** @hide */
@Override
- public boolean hasExtension() {
- return mFromInsets.left < 0 || mFromInsets.top < 0 || mFromInsets.right < 0
- || mFromInsets.bottom < 0;
+ @WindowInsets.Side.InsetsSide
+ public int getExtensionEdges() {
+ mExtensionEdges = 0x0;
+ if (mFromLeftValue > 0 || mToLeftValue > 0) {
+ mExtensionEdges |= WindowInsets.Side.LEFT;
+ }
+ if (mFromRightValue > 0 || mToRightValue > 0) {
+ mExtensionEdges |= WindowInsets.Side.RIGHT;
+ }
+ if (mFromTopValue > 0 || mToTopValue > 0) {
+ mExtensionEdges |= WindowInsets.Side.TOP;
+ }
+ if (mFromBottomValue > 0 || mToBottomValue > 0) {
+ mExtensionEdges |= WindowInsets.Side.BOTTOM;
+ }
+ return mExtensionEdges;
}
@Override
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 07a9794..a4ca55e 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -196,16 +196,20 @@
/**
* Invokes {@link IInputMethodManager#removeImeSurface()}
+ *
+ * @param displayId display ID from which this request originates
+ * @param exceptionHandler an optional {@link RemoteException} handler
*/
@AnyThread
@RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
- static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
+ static void removeImeSurface(int displayId,
+ @Nullable Consumer<RemoteException> exceptionHandler) {
final IInputMethodManager service = getService();
if (service == null) {
return;
}
try {
- service.removeImeSurface();
+ service.removeImeSurface(displayId);
} catch (RemoteException e) {
handleRemoteExceptionOrRethrow(e, exceptionHandler);
}
diff --git a/core/java/android/view/inputmethod/InputMethodManagerGlobal.java b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
index 5df9fd1..244b239 100644
--- a/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
+++ b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
@@ -95,11 +95,13 @@
/**
* Invokes {@link IInputMethodManager#removeImeSurface()}
*
+ * @param displayId display ID from which this request originates.
* @param exceptionHandler an optional {@link RemoteException} handler.
*/
@AnyThread
@RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
- public static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
- IInputMethodManagerGlobalInvoker.removeImeSurface(exceptionHandler);
+ public static void removeImeSurface(int displayId,
+ @Nullable Consumer<RemoteException> exceptionHandler) {
+ IInputMethodManagerGlobalInvoker.removeImeSurface(displayId, exceptionHandler);
}
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d40eeda..8b6ead7 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -76,7 +76,6 @@
import android.text.Spanned;
import android.text.SpannedString;
import android.text.StaticLayout;
-import android.text.TextFlags;
import android.text.TextUtils;
import android.text.method.InsertModeTransformationMethod;
import android.text.method.KeyListener;
@@ -471,7 +470,6 @@
private static final int LINE_CHANGE_SLOP_MIN_DP = 8;
private int mLineChangeSlopMax;
private int mLineChangeSlopMin;
- private boolean mUseNewContextMenu;
private final AccessibilitySmartActions mA11ySmartActions;
private InsertModeController mInsertModeController;
@@ -502,9 +500,6 @@
mLineSlopRatio = AppGlobals.getFloatCoreSetting(
WidgetFlags.KEY_LINE_SLOP_RATIO,
WidgetFlags.LINE_SLOP_RATIO_DEFAULT);
- mUseNewContextMenu = AppGlobals.getIntCoreSetting(
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
if (TextView.DEBUG_CURSOR) {
logCursor("Editor", "Cursor drag from anywhere is %s.",
mFlagCursorDragFromAnywhereEnabled ? "enabled" : "disabled");
@@ -3216,29 +3211,18 @@
final int menuItemOrderCut = 4;
final int menuItemOrderCopy = 5;
final int menuItemOrderPaste = 6;
- final int menuItemOrderPasteAsPlainText;
- final int menuItemOrderSelectAll;
- final int menuItemOrderShare;
- final int menuItemOrderAutofill;
- if (mUseNewContextMenu) {
- menuItemOrderPasteAsPlainText = 7;
- menuItemOrderSelectAll = 8;
- menuItemOrderShare = 9;
- menuItemOrderAutofill = 10;
+ final int menuItemOrderPasteAsPlainText = 7;
+ final int menuItemOrderSelectAll = 8;
+ final int menuItemOrderShare = 9;
+ final int menuItemOrderAutofill = 10;
- menu.setOptionalIconsVisible(true);
- menu.setGroupDividerEnabled(true);
+ menu.setOptionalIconsVisible(true);
+ menu.setGroupDividerEnabled(true);
- setAssistContextMenuItems(menu);
+ setAssistContextMenuItems(menu);
- final int keyboard = mTextView.getResources().getConfiguration().keyboard;
- menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY);
- } else {
- menuItemOrderShare = 7;
- menuItemOrderSelectAll = 8;
- menuItemOrderAutofill = 10;
- menuItemOrderPasteAsPlainText = 11;
- }
+ final int keyboard = mTextView.getResources().getConfiguration().keyboard;
+ menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY);
final TypedArray a = mTextView.getContext().obtainStyledAttributes(new int[] {
// TODO: Make Undo/Redo be public attribute.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f7e0ec8..9099db8 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -105,7 +105,6 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.text.BoringLayout;
-import android.text.ClientFlags;
import android.text.DynamicLayout;
import android.text.Editable;
import android.text.GetChars;
@@ -1659,7 +1658,7 @@
if (!hasUseBoundForWidthValue) {
if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
- mUseBoundsForWidth = ClientFlags.useBoundsForWidth();
+ mUseBoundsForWidth = Flags.useBoundsForWidth();
} else {
mUseBoundsForWidth = false;
}
@@ -9732,7 +9731,7 @@
break;
case KeyEvent.KEYCODE_ESCAPE:
- if (com.android.text.flags.Flags.escapeClearsFocus() && event.hasNoModifiers()) {
+ if (Flags.escapeClearsFocus() && event.hasNoModifiers()) {
if (mEditor != null && mEditor.getTextActionMode() != null) {
stopTextActionMode();
return KEY_EVENT_HANDLED;
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index ec4e3e9..3cfde87 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -30,6 +30,8 @@
import android.os.Parcelable;
import android.view.WindowManager;
+import com.android.window.flags.Flags;
+
/**
* A parcelable filter that can be used for rerouting transitions to a remote. This is a local
* representation so that the transition system doesn't need to make blocking queries over
@@ -183,6 +185,9 @@
public ComponentName mTopActivity;
public IBinder mLaunchCookie;
+ /** If non-null, requires the change to specifically have or not-have a custom animation. */
+ public Boolean mCustomAnimation = null;
+
public Requirement() {
}
@@ -196,6 +201,9 @@
mOrder = in.readInt();
mTopActivity = in.readTypedObject(ComponentName.CREATOR);
mLaunchCookie = in.readStrongBinder();
+ // 0: null, 1: false, 2: true
+ final int customAnimRaw = in.readInt();
+ mCustomAnimation = customAnimRaw == 0 ? null : Boolean.valueOf(customAnimRaw == 2);
}
/** Go through changes and find if at-least one change matches this filter */
@@ -237,6 +245,23 @@
if (!matchesCookie(change.getTaskInfo())) {
continue;
}
+ if (mCustomAnimation != null
+ // only applies to activity/task
+ && (change.getTaskInfo() != null
+ || change.getActivityComponent() != null)) {
+ final TransitionInfo.AnimationOptions opts =
+ Flags.moveAnimationOptionsToChange() ? change.getAnimationOptions()
+ : info.getAnimationOptions();
+ if (opts != null) {
+ boolean canActuallyOverride = change.getTaskInfo() == null
+ || opts.getOverrideTaskTransition();
+ if (mCustomAnimation != canActuallyOverride) {
+ continue;
+ }
+ } else if (mCustomAnimation) {
+ continue;
+ }
+ }
return true;
}
return false;
@@ -286,6 +311,8 @@
dest.writeInt(mOrder);
dest.writeTypedObject(mTopActivity, flags);
dest.writeStrongBinder(mLaunchCookie);
+ int customAnimRaw = mCustomAnimation == null ? 0 : (mCustomAnimation ? 2 : 1);
+ dest.writeInt(customAnimRaw);
}
@NonNull
@@ -327,6 +354,9 @@
out.append(" order=" + containerOrderToString(mOrder));
out.append(" topActivity=").append(mTopActivity);
out.append(" launchCookie=").append(mLaunchCookie);
+ if (mCustomAnimation != null) {
+ out.append(" customAnim=").append(mCustomAnimation.booleanValue());
+ }
out.append("}");
return out.toString();
}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 76989f9..725d496 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -158,17 +158,6 @@
}
flag {
- name: "keyguard_appear_transition"
- namespace: "windowing_frontend"
- description: "Add transition when keyguard appears"
- bug: "327970608"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "get_dimmer_on_closing"
namespace: "windowing_frontend"
description: "Change check for when to ignore a closing task's dim"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f8a2a31..4230641 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -123,17 +123,6 @@
flag {
namespace: "windowing_sdk"
- name: "disable_object_pool"
- description: "Whether to disable object pool and let the GC handle lifecycle items"
- bug: "311089192"
- is_fixed_read_only: true
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
- namespace: "windowing_sdk"
name: "rear_display_disable_force_desktop_system_decorations"
description: "Block system decorations from being added to a rear display when desktop mode is forced"
bug: "346103150"
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 87e22ed..4828393 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -29,6 +29,7 @@
import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -36,6 +37,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.flags.Flags.customizableWindowHeaders;
import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL;
@@ -226,6 +228,7 @@
private boolean mLastHasLeftStableInset = false;
private int mLastWindowFlags = 0;
private @InsetsType int mLastForceConsumingTypes = 0;
+ private boolean mLastForceConsumingOpaqueCaptionBar = false;
private @InsetsType int mLastSuppressScrimTypes = 0;
private int mRootScrollY = 0;
@@ -1068,8 +1071,12 @@
WindowManager.LayoutParams attrs = mWindow.getAttributes();
int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
+ final ViewRootImpl viewRoot = getViewRootImpl();
final WindowInsetsController controller = getWindowInsetsController();
final @InsetsType int requestedVisibleTypes = controller.getRequestedVisibleTypes();
+ final @Appearance int appearance = viewRoot != null
+ ? viewRoot.mWindowAttributes.insetsFlags.appearance
+ : controller.getSystemBarsAppearance();
// IME is an exceptional floating window that requires color view.
final boolean isImeWindow =
@@ -1080,13 +1087,9 @@
& FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
mLastWindowFlags = attrs.flags;
- final ViewRootImpl viewRoot = getViewRootImpl();
- final @Appearance int appearance = viewRoot != null
- ? viewRoot.mWindowAttributes.insetsFlags.appearance
- : controller.getSystemBarsAppearance();
-
if (insets != null) {
mLastForceConsumingTypes = insets.getForceConsumingTypes();
+ mLastForceConsumingOpaqueCaptionBar = insets.isForceConsumingOpaqueCaptionBar();
final boolean clearsCompatInsets = clearsCompatInsets(attrs.type, attrs.flags,
getResources().getConfiguration().windowConfiguration.getActivityType(),
@@ -1209,16 +1212,20 @@
final boolean hideCaptionBar = fullscreen
|| (requestedVisibleTypes & WindowInsets.Type.captionBar()) == 0;
- final boolean consumingCaptionBar =
- ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
+ final boolean consumingCaptionBar = Flags.enableCaptionCompatInsetForceConsumption()
+ && ((mLastForceConsumingTypes & WindowInsets.Type.captionBar()) != 0
&& hideCaptionBar);
- final int consumedTop;
- if (Flags.enableCaptionCompatInsetForceConsumption()) {
- consumedTop = (consumingStatusBar || consumingCaptionBar) ? mLastTopInset : 0;
- } else {
- consumedTop = consumingStatusBar ? mLastTopInset : 0;
- }
+ final boolean isOpaqueCaptionBar = customizableWindowHeaders()
+ && (appearance & APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) == 0;
+ final boolean consumingOpaqueCaptionBar =
+ Flags.enableCaptionCompatInsetForceConsumptionAlways()
+ && mLastForceConsumingOpaqueCaptionBar
+ && isOpaqueCaptionBar;
+
+ final int consumedTop =
+ (consumingStatusBar || consumingCaptionBar || consumingOpaqueCaptionBar)
+ ? mLastTopInset : 0;
int consumedRight = consumingNavBar ? mLastRightInset : 0;
int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 42be4fc..ec004d0 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -2501,6 +2501,9 @@
if (mEdgeToEdgeEnforced) {
getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
mDecorFitsSystemWindows = false;
+ mStatusBarColor = Color.TRANSPARENT;
+ mNavigationBarDividerColor = Color.TRANSPARENT;
+ // mNavigationBarColor is not reset here because it might be used to draw the scrim.
}
if (CompatChanges.isChangeEnabled(OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE)
&& !a.getBoolean(R.styleable.Window_windowOptOutEdgeToEdgeEnforcement,
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 792c223..f177e14 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -82,4 +82,5 @@
void onCallBackModeStopped(int type, int reason);
void onSimultaneousCallingStateChanged(in int[] subIds);
void onCarrierRoamingNtnModeChanged(in boolean active);
+ void onCarrierRoamingNtnEligibleStateChanged(in boolean eligible);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 04332cd..e500a37a 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -121,4 +121,5 @@
void notifyCallbackModeStarted(int phoneId, int subId, int type);
void notifyCallbackModeStopped(int phoneId, int subId, int type, int reason);
void notifyCarrierRoamingNtnModeChanged(int subId, in boolean active);
+ void notifyCarrierRoamingNtnEligibleStateChanged(int subId, in boolean eligible);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index cba27ce..3f7ba0a 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -171,7 +171,7 @@
@EnforcePermission("INTERNAL_SYSTEM_WINDOW")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
- void removeImeSurface();
+ void removeImeSurface(int displayId);
/** Remove the IME surface. Requires passing the currently focused window. */
oneway void removeImeSurfaceFromWindowAsync(in IBinder windowToken);
diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
index f08e860..c24cf5f 100644
--- a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
@@ -5,14 +5,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
-import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Parcelable;
import android.os.SystemClock;
-import android.text.TextFlags;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -217,11 +215,7 @@
mSubMenuHoverHandler = new Handler();
- mItemLayout = AppGlobals.getIntCoreSetting(
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0
- ? com.android.internal.R.layout.cascading_menu_item_layout_material
- : com.android.internal.R.layout.cascading_menu_item_layout;
+ mItemLayout = com.android.internal.R.layout.cascading_menu_item_layout_material;
}
@Override
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 468705d..b73cacb 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -16,13 +16,10 @@
package com.android.internal.view.menu;
-import android.app.AppGlobals;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.text.ClientFlags;
-import android.text.TextFlags;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -36,6 +33,8 @@
import android.widget.RadioButton;
import android.widget.TextView;
+import com.android.text.flags.Flags;
+
/**
* The item view for each item in the ListView-based MenuViews.
*/
@@ -62,8 +61,6 @@
private int mMenuType;
- private boolean mUseNewContextMenu;
-
private LayoutInflater mInflater;
private boolean mForceShowIcon;
@@ -90,10 +87,6 @@
a.recycle();
b.recycle();
-
- mUseNewContextMenu = AppGlobals.getIntCoreSetting(
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
}
public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -291,7 +284,7 @@
private void insertIconView() {
LayoutInflater inflater = getInflater();
mIconView = (ImageView) inflater.inflate(
- mUseNewContextMenu && !ClientFlags.fixMisalignedContextMenu()
+ !Flags.fixMisalignedContextMenu()
? com.android.internal.R.layout.list_menu_item_fixed_size_icon :
com.android.internal.R.layout.list_menu_item_icon,
this, false);
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index c254e99..c43a8c6 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -16,12 +16,9 @@
package com.android.internal.view.menu;
-import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Resources;
import android.os.Parcelable;
-import android.text.ClientFlags;
-import android.text.TextFlags;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -38,6 +35,8 @@
import android.widget.PopupWindow.OnDismissListener;
import android.widget.TextView;
+import com.android.text.flags.Flags;
+
import java.util.Objects;
/**
@@ -120,16 +119,11 @@
public StandardMenuPopup(Context context, MenuBuilder menu, View anchorView, int popupStyleAttr,
int popupStyleRes, boolean overflowOnly) {
mContext = Objects.requireNonNull(context);
- boolean useNewContextMenu = AppGlobals.getIntCoreSetting(
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
-
mMenu = menu;
mOverflowOnly = overflowOnly;
final LayoutInflater inflater = LayoutInflater.from(context);
mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly,
- ClientFlags.fixMisalignedContextMenu() && useNewContextMenu
- ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT);
+ Flags.fixMisalignedContextMenu() ? ITEM_LAYOUT_MATERIAL : ITEM_LAYOUT);
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 809ec63..e5ac0e1 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -33,6 +33,7 @@
#include <algorithm>
#include <array>
+#include <cstring>
#include <limits>
#include <memory>
#include <string>
@@ -50,7 +51,6 @@
#include <inttypes.h>
#include <pwd.h>
#include <signal.h>
-#include <string.h>
#include <sys/epoll.h>
#include <sys/errno.h>
#include <sys/pidfd.h>
@@ -73,13 +73,13 @@
// readProcFile() are reading files under this threshold, e.g.,
// /proc/pid/stat. /proc/pid/time_in_state ends up being about 520
// bytes, so use 1024 for the stack to provide a bit of slack.
-static constexpr ssize_t kProcReadStackBufferSize = 1024;
+static constexpr size_t kProcReadStackBufferSize = 1024;
// The other files we read from proc tend to be a bit larger (e.g.,
// /proc/stat is about 3kB), so once we exhaust the stack buffer,
// retry with a relatively large heap-allocated buffer. We double
// this size and retry until the whole file fits.
-static constexpr ssize_t kProcReadMinHeapBufferSize = 4096;
+static constexpr size_t kProcReadMinHeapBufferSize = 4096;
#if GUARD_THREAD_PRIORITY
Mutex gKeyCreateMutex;
@@ -817,7 +817,6 @@
}
DIR* dirp = opendir(file8);
-
env->ReleaseStringUTFChars(file, file8);
if(dirp == NULL) {
@@ -850,6 +849,7 @@
jintArray newArray = env->NewIntArray(newCount);
if (newArray == NULL) {
closedir(dirp);
+ if (curData) env->ReleaseIntArrayElements(lastArray, curData, 0);
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return NULL;
}
@@ -1046,78 +1046,71 @@
return JNI_FALSE;
}
- const char* file8 = env->GetStringUTFChars(file, NULL);
- if (file8 == NULL) {
+ auto releaser = [&](const char* jniStr) { env->ReleaseStringUTFChars(file, jniStr); };
+ std::unique_ptr<const char[], decltype(releaser)> file8(env->GetStringUTFChars(file, NULL),
+ releaser);
+ if (!file8) {
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return JNI_FALSE;
}
- ::android::base::unique_fd fd(open(file8, O_RDONLY | O_CLOEXEC));
+ ::android::base::unique_fd fd(open(file8.get(), O_RDONLY | O_CLOEXEC));
if (!fd.ok()) {
if (kDebugProc) {
- ALOGW("Unable to open process file: %s\n", file8);
+ ALOGW("Unable to open process file: %s\n", file8.get());
}
- env->ReleaseStringUTFChars(file, file8);
return JNI_FALSE;
}
- env->ReleaseStringUTFChars(file, file8);
// Most proc files we read are small, so we go through the loop
- // with the stack buffer firstly. We allocate a buffer big
- // enough for the whole file.
+ // with the stack buffer first. We allocate a buffer big enough
+ // for most files.
- char readBufferStack[kProcReadStackBufferSize];
- std::unique_ptr<char[]> readBufferHeap;
- char* readBuffer = &readBufferStack[0];
- ssize_t readBufferSize = kProcReadStackBufferSize;
- ssize_t numberBytesRead;
+ char stackBuf[kProcReadStackBufferSize];
+ std::vector<char> heapBuf;
+ char* buf = stackBuf;
+
+ size_t remaining = sizeof(stackBuf);
off_t offset = 0;
- for (;;) {
- ssize_t requestedBufferSize = readBufferSize - offset;
- // By using pread, we can avoid an lseek to rewind the FD
- // before retry, saving a system call.
- numberBytesRead =
- TEMP_FAILURE_RETRY(pread(fd, readBuffer + offset, requestedBufferSize, offset));
- if (numberBytesRead < 0) {
+ ssize_t numBytesRead;
+
+ do {
+ numBytesRead = TEMP_FAILURE_RETRY(pread(fd, buf + offset, remaining, offset));
+ if (numBytesRead < 0) {
if (kDebugProc) {
ALOGW("Unable to read process file err: %s file: %s fd=%d\n",
- strerror_r(errno, &readBufferStack[0], sizeof(readBufferStack)), file8,
- fd.get());
+ strerror_r(errno, stackBuf, sizeof(stackBuf)), file8.get(), fd.get());
}
return JNI_FALSE;
}
- if (numberBytesRead == 0) {
- // End of file.
- numberBytesRead = offset;
- break;
- }
- if (numberBytesRead < requestedBufferSize) {
- // Read less bytes than requested, it's not an error per pread(2).
- offset += numberBytesRead;
- } else {
- // Buffer is fully used, try to grow it.
- if (readBufferSize > std::numeric_limits<ssize_t>::max() / 2) {
- if (kDebugProc) {
- ALOGW("Proc file too big: %s fd=%d\n", file8, fd.get());
+
+ offset += numBytesRead;
+ remaining -= numBytesRead;
+
+ if (numBytesRead && !remaining) {
+ if (buf == stackBuf) {
+ heapBuf.resize(kProcReadMinHeapBufferSize);
+ static_assert(kProcReadMinHeapBufferSize > sizeof(stackBuf));
+ std::memcpy(heapBuf.data(), stackBuf, sizeof(stackBuf));
+ } else {
+ constexpr size_t MAX_READABLE_PROCFILE_SIZE = 64 << 20;
+ if (heapBuf.size() >= MAX_READABLE_PROCFILE_SIZE) {
+ if (kDebugProc) {
+ ALOGW("Proc file too big: %s fd=%d size=%zu\n",
+ file8.get(), fd.get(), heapBuf.size());
+ }
+ return JNI_FALSE;
}
- return JNI_FALSE;
+ heapBuf.resize(2 * heapBuf.size());
}
- readBufferSize = std::max(readBufferSize * 2, kProcReadMinHeapBufferSize);
- readBufferHeap.reset(); // Free address space before getting more.
- readBufferHeap = std::make_unique<char[]>(readBufferSize);
- if (!readBufferHeap) {
- jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
- return JNI_FALSE;
- }
- readBuffer = readBufferHeap.get();
- offset = 0;
+ buf = heapBuf.data();
+ remaining = heapBuf.size() - offset;
}
- }
+ } while (numBytesRead != 0);
// parseProcLineArray below modifies the buffer while parsing!
return android_os_Process_parseProcLineArray(
- env, clazz, readBuffer, 0, numberBytesRead,
- format, outStrings, outLongs, outFloats);
+ env, clazz, buf, 0, offset, format, outStrings, outLongs, outFloats);
}
void android_os_Process_setApplicationObject(JNIEnv* env, jobject clazz,
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 9ce7658..0f53164 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/EdgeExtensionParameters.h>
#include <android/gui/JankData.h>
#include <android/hardware/display/IDeviceProductInfoConstants.h>
#include <android/os/IInputConstants.h>
@@ -799,6 +800,20 @@
transaction->setStretchEffect(ctrl, stretch);
}
+static void nativeSetEdgeExtensionEffect(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObj, jboolean leftEdge, jboolean rightEdge,
+ jboolean topEdge, jboolean bottomEdge) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ auto* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObj);
+
+ auto effect = gui::EdgeExtensionParameters();
+ effect.extendLeft = leftEdge;
+ effect.extendRight = rightEdge;
+ effect.extendTop = topEdge;
+ effect.extendBottom = bottomEdge;
+ transaction->setEdgeExtensionEffect(ctrl, effect);
+}
+
static void nativeSetFlags(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jint flags, jint mask) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2340,6 +2355,8 @@
(void*)nativeSetBlurRegions },
{"nativeSetStretchEffect", "(JJFFFFFFFFFF)V",
(void*) nativeSetStretchEffect },
+ {"nativeSetEdgeExtensionEffect", "(JJZZZZ)V",
+ (void*) nativeSetEdgeExtensionEffect },
{"nativeSetShadowRadius", "(JJF)V",
(void*)nativeSetShadowRadius },
{"nativeSetFrameRate", "(JJFII)V",
diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto
index e560a94..8ee7962 100644
--- a/core/proto/android/app/appexitinfo.proto
+++ b/core/proto/android/app/appexitinfo.proto
@@ -20,7 +20,7 @@
package android.app;
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
/**
* An android.app.ApplicationExitInfo object.
diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto
index c1375334..8de5458 100644
--- a/core/proto/android/app/appstartinfo.proto
+++ b/core/proto/android/app/appstartinfo.proto
@@ -20,7 +20,7 @@
package android.app;
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
/**
* An android.app.ApplicationStartInfo object.
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 90069f1..58f39a9 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -35,7 +35,7 @@
import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
import "frameworks/base/core/proto/android/util/common.proto";
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
option java_multiple_files = true;
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 593bbc6..8fd5d71 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -26,7 +26,7 @@
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto";
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
+import "frameworks/proto_logging/stats/enums/app_shared/app_enums.proto";
import "frameworks/proto_logging/stats/enums/os/enums.proto";
import "frameworks/proto_logging/stats/enums/view/enums.proto";
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8cb7646..2afc303 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7100,4 +7100,8 @@
<!-- Whether to enable the private space search illustration and search tile content in "Hide Private Space" settings page.
OEM/Partner can explicitly opt to hide the illustration and search tile content. -->
<bool name="config_enableSearchTileHideIllustrationInPrivateSpace">true</bool>
+
+ <!-- The maximum number of call log entries for each sim card that can be stored in the call log
+ provider on the device. -->
+ <integer name="config_maximumCallLogEntriesPerSim">500</integer>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4a425cb..bc8c778 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5530,6 +5530,8 @@
<java-symbol type="integer" name="config_wallpaperFrameRateCompatibility" />
<java-symbol type="integer" name="config_defaultMinEmergencyGestureTapDurationMillis" />
+ <java-symbol type="integer" name="config_maximumCallLogEntriesPerSim" />
+
<!-- Back swipe thresholds -->
<java-symbol type="dimen" name="navigation_edge_action_progress_threshold" />
<java-symbol type="dimen" name="back_progress_non_linear_factor" />
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
index eebc578..ef7df59 100644
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
@@ -29,9 +29,9 @@
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityTestActivity;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
index 92b1c04..1d360cc 100644
--- a/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/AbstractCrossUserContentResolverTest.java
@@ -18,7 +18,6 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
-import org.junit.Ignore;
import android.app.ActivityManager;
import android.app.activity.LocalProvider;
@@ -33,13 +32,14 @@
import android.os.UserManager;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.SystemUtil;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/ApexEnvironmentTest.java b/core/tests/coretests/src/android/content/ApexEnvironmentTest.java
index 438c5ae..f3803d2 100644
--- a/core/tests/coretests/src/android/content/ApexEnvironmentTest.java
+++ b/core/tests/coretests/src/android/content/ApexEnvironmentTest.java
@@ -20,8 +20,8 @@
import android.os.UserHandle;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
index 407c6c3..a9e781c 100644
--- a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
+++ b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
@@ -19,8 +19,8 @@
import static org.junit.Assert.fail;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/content/ContentProviderTest.java b/core/tests/coretests/src/android/content/ContentProviderTest.java
index c9a6d22..4ecd2da 100644
--- a/core/tests/coretests/src/android/content/ContentProviderTest.java
+++ b/core/tests/coretests/src/android/content/ContentProviderTest.java
@@ -26,7 +26,7 @@
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java
index c8015d4..dfde0bc 100644
--- a/core/tests/coretests/src/android/content/ContentResolverTest.java
+++ b/core/tests/coretests/src/android/content/ContentResolverTest.java
@@ -39,7 +39,7 @@
import android.util.Size;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/content/PermissionCheckerTest.java b/core/tests/coretests/src/android/content/PermissionCheckerTest.java
index cb04a74..65d8e2b 100644
--- a/core/tests/coretests/src/android/content/PermissionCheckerTest.java
+++ b/core/tests/coretests/src/android/content/PermissionCheckerTest.java
@@ -23,7 +23,7 @@
import android.os.Binder;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java b/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java
index 4366e02c..7799185 100644
--- a/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java
+++ b/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java
@@ -22,7 +22,7 @@
import android.platform.test.annotations.AppModeFull;
import android.text.TextUtils;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
index 86e95832..ad3a16b 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerPropertyTests.java
@@ -19,15 +19,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
import android.content.pm.PackageManager.Property;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
index 20421d1..b60d614 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
@@ -18,8 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java b/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java
index 61a3a11..dbd6c2b 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserCacheHelperTest.java
@@ -24,8 +24,8 @@
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java b/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java
index 2986d61..6c23ea3 100644
--- a/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackagePartitionsTest.java
@@ -24,7 +24,7 @@
import android.os.SystemProperties;
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java
index 8f8488f..ec5e205 100644
--- a/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java
+++ b/core/tests/coretests/src/android/content/pm/ShortcutQueryWrapperTest.java
@@ -23,7 +23,7 @@
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.collect.Lists;
diff --git a/core/tests/coretests/src/android/content/pm/UserInfoTest.java b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
index af36dbb..edeea6d 100644
--- a/core/tests/coretests/src/android/content/pm/UserInfoTest.java
+++ b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
@@ -20,8 +20,8 @@
import android.os.UserHandle;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
index 9aef2ca..85f5d69 100644
--- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
+++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
@@ -30,8 +30,8 @@
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java b/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java
index f7f9569..ac69a0f 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesDrawableTest.java
@@ -27,8 +27,8 @@
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/database/CursorWindowTest.java b/core/tests/coretests/src/android/database/CursorWindowTest.java
index 255020a..64e4d3b 100644
--- a/core/tests/coretests/src/android/database/CursorWindowTest.java
+++ b/core/tests/coretests/src/android/database/CursorWindowTest.java
@@ -23,8 +23,8 @@
import android.database.sqlite.SQLiteException;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/database/DatabaseUtilsTest.java b/core/tests/coretests/src/android/database/DatabaseUtilsTest.java
index e25fdf9..c00c171 100644
--- a/core/tests/coretests/src/android/database/DatabaseUtilsTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseUtilsTest.java
@@ -16,20 +16,19 @@
package android.database;
-import static android.database.DatabaseUtils.bindSelection;
-import static android.database.DatabaseUtils.getSqlStatementType;
-import static android.database.DatabaseUtils.getSqlStatementTypeExtended;
-import static android.database.DatabaseUtils.STATEMENT_COMMENT;
import static android.database.DatabaseUtils.STATEMENT_CREATE;
import static android.database.DatabaseUtils.STATEMENT_DDL;
import static android.database.DatabaseUtils.STATEMENT_OTHER;
import static android.database.DatabaseUtils.STATEMENT_SELECT;
import static android.database.DatabaseUtils.STATEMENT_UPDATE;
import static android.database.DatabaseUtils.STATEMENT_WITH;
+import static android.database.DatabaseUtils.bindSelection;
+import static android.database.DatabaseUtils.getSqlStatementType;
+import static android.database.DatabaseUtils.getSqlStatementTypeExtended;
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/database/RedactingCursorTest.java b/core/tests/coretests/src/android/database/RedactingCursorTest.java
index e2d2bae..470d4a9 100644
--- a/core/tests/coretests/src/android/database/RedactingCursorTest.java
+++ b/core/tests/coretests/src/android/database/RedactingCursorTest.java
@@ -24,7 +24,7 @@
import android.net.Uri;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
index 9bad6d2..efa9b7a 100644
--- a/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
+++ b/core/tests/coretests/src/android/database/SQLiteOpenHelperTest.java
@@ -29,8 +29,8 @@
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java
index 09c380a..e20a806 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCantOpenDatabaseExceptionTest.java
@@ -20,14 +20,12 @@
import static org.junit.Assert.fail;
import android.content.Context;
-import android.database.sqlite.SQLiteCantOpenDatabaseException;
-import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.OpenParams;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java
index 82bd588..a4dedc5 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCompatibilityWalFlagsTest.java
@@ -25,8 +25,8 @@
import android.database.DatabaseUtils;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java
index 2b663bd..dbe7a9a 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java
@@ -24,8 +24,8 @@
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index c4695d9..bd9c4b8 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -25,16 +25,13 @@
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
-import android.os.SystemClock;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.test.AndroidTestCase;
-import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
@@ -46,11 +43,9 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Phaser;
-import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 24d27c4..832ebe5 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -29,9 +29,9 @@
import android.os.SystemClock;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java
index a9d1482..ced1846 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java
@@ -20,7 +20,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
index eff52f0..5ef1460 100644
--- a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
+++ b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
@@ -32,7 +32,7 @@
import android.util.Pair;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver;
diff --git a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
index 84d2995..4dfe2e2 100644
--- a/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
+++ b/core/tests/coretests/src/android/os/BinderProxyCountingTest.java
@@ -29,8 +29,8 @@
import android.util.Log;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
diff --git a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
index 552066c..98e96c2 100644
--- a/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/BinderWorkSourceTest.java
@@ -29,13 +29,12 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/BluetoothBatteryStatsTest.java b/core/tests/coretests/src/android/os/BluetoothBatteryStatsTest.java
index e12ca24a..3af35f7 100644
--- a/core/tests/coretests/src/android/os/BluetoothBatteryStatsTest.java
+++ b/core/tests/coretests/src/android/os/BluetoothBatteryStatsTest.java
@@ -18,7 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/BroadcasterTest.java b/core/tests/coretests/src/android/os/BroadcasterTest.java
index 7829457..31a7063 100644
--- a/core/tests/coretests/src/android/os/BroadcasterTest.java
+++ b/core/tests/coretests/src/android/os/BroadcasterTest.java
@@ -18,8 +18,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index 2a718ff..e8c701e 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -23,8 +23,8 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import junit.framework.Assert;
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index 40e79ad..ded6fc5 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -30,8 +30,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
index c2cea0a..2117e74 100644
--- a/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
+++ b/core/tests/coretests/src/android/os/CancellationSignalBeamerTest.java
@@ -27,9 +27,9 @@
import android.util.PollingCheck;
import android.util.PollingCheck.PollingCheckCondition;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/os/CancellationSignalTest.java b/core/tests/coretests/src/android/os/CancellationSignalTest.java
index 5cd2873..8e11df5 100644
--- a/core/tests/coretests/src/android/os/CancellationSignalTest.java
+++ b/core/tests/coretests/src/android/os/CancellationSignalTest.java
@@ -21,7 +21,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/EnvironmentTest.java b/core/tests/coretests/src/android/os/EnvironmentTest.java
index 1aa263f..1b49624 100644
--- a/core/tests/coretests/src/android/os/EnvironmentTest.java
+++ b/core/tests/coretests/src/android/os/EnvironmentTest.java
@@ -32,7 +32,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 78a2c1c..66b22a8 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -60,7 +60,7 @@
import android.system.Os;
import android.util.DataUnit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
@@ -70,8 +70,6 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -79,11 +77,11 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
-import java.net.InetSocketAddress;
@RunWith(AndroidJUnit4.class)
public class FileUtilsTest {
diff --git a/core/tests/coretests/src/android/os/HandlerThreadTest.java b/core/tests/coretests/src/android/os/HandlerThreadTest.java
index 3237812..a4eda06 100644
--- a/core/tests/coretests/src/android/os/HandlerThreadTest.java
+++ b/core/tests/coretests/src/android/os/HandlerThreadTest.java
@@ -25,8 +25,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Assume;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/os/IdleHandlerTest.java b/core/tests/coretests/src/android/os/IdleHandlerTest.java
index 8644663..7accb3b 100644
--- a/core/tests/coretests/src/android/os/IdleHandlerTest.java
+++ b/core/tests/coretests/src/android/os/IdleHandlerTest.java
@@ -19,8 +19,8 @@
import android.os.MessageQueue.IdleHandler;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java
index 0025e3a..251e00f 100644
--- a/core/tests/coretests/src/android/os/LocaleListTest.java
+++ b/core/tests/coretests/src/android/os/LocaleListTest.java
@@ -23,8 +23,8 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java
index 8cd6773..549e666 100644
--- a/core/tests/coretests/src/android/os/MessageQueueTest.java
+++ b/core/tests/coretests/src/android/os/MessageQueueTest.java
@@ -18,9 +18,9 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/PackageTagsListTest.java b/core/tests/coretests/src/android/os/PackageTagsListTest.java
index 9034a5c..6c9d7ebc 100644
--- a/core/tests/coretests/src/android/os/PackageTagsListTest.java
+++ b/core/tests/coretests/src/android/os/PackageTagsListTest.java
@@ -24,7 +24,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/ParcelNullabilityTest.java b/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
index ffeab29..09395f1 100644
--- a/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
+++ b/core/tests/coretests/src/android/os/ParcelNullabilityTest.java
@@ -24,8 +24,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.ArrayMap;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index 0697c96..0373231 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -28,7 +28,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Log;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java
index 46f1706..436720e 100644
--- a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java
@@ -25,9 +25,9 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 93a5642..4b49fde 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -28,7 +28,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index b9a12ad..3b27fc0 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -36,8 +36,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import org.junit.After;
diff --git a/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java b/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java
index 25ce240..e22c862 100644
--- a/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java
+++ b/core/tests/coretests/src/android/os/RedactingFileDescriptorTest.java
@@ -29,7 +29,7 @@
import android.system.Os;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/os/RemoteCallbackListTest.java b/core/tests/coretests/src/android/os/RemoteCallbackListTest.java
index cc342cf..7d694a0 100644
--- a/core/tests/coretests/src/android/os/RemoteCallbackListTest.java
+++ b/core/tests/coretests/src/android/os/RemoteCallbackListTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/RemoteCallbackTest.java b/core/tests/coretests/src/android/os/RemoteCallbackTest.java
index 192c3d01..ddcc380 100644
--- a/core/tests/coretests/src/android/os/RemoteCallbackTest.java
+++ b/core/tests/coretests/src/android/os/RemoteCallbackTest.java
@@ -21,7 +21,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/ResultReceiverTest.java b/core/tests/coretests/src/android/os/ResultReceiverTest.java
index 8ee873b..be67825 100644
--- a/core/tests/coretests/src/android/os/ResultReceiverTest.java
+++ b/core/tests/coretests/src/android/os/ResultReceiverTest.java
@@ -21,7 +21,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/StrictModeTest.java b/core/tests/coretests/src/android/os/StrictModeTest.java
index 9050583..7f8af83 100644
--- a/core/tests/coretests/src/android/os/StrictModeTest.java
+++ b/core/tests/coretests/src/android/os/StrictModeTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/TestLooperManagerTest.java b/core/tests/coretests/src/android/os/TestLooperManagerTest.java
index 5959444..4d64a3a 100644
--- a/core/tests/coretests/src/android/os/TestLooperManagerTest.java
+++ b/core/tests/coretests/src/android/os/TestLooperManagerTest.java
@@ -24,8 +24,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/TimestampedValueTest.java b/core/tests/coretests/src/android/os/TimestampedValueTest.java
index f36d9e6..5bb7eb5 100644
--- a/core/tests/coretests/src/android/os/TimestampedValueTest.java
+++ b/core/tests/coretests/src/android/os/TimestampedValueTest.java
@@ -20,7 +20,7 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java
index b2c005f..5462f32 100644
--- a/core/tests/coretests/src/android/os/TraceTest.java
+++ b/core/tests/coretests/src/android/os/TraceTest.java
@@ -20,10 +20,10 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/os/UserHandleTest.java b/core/tests/coretests/src/android/os/UserHandleTest.java
index 160b20d..168f2a6 100644
--- a/core/tests/coretests/src/android/os/UserHandleTest.java
+++ b/core/tests/coretests/src/android/os/UserHandleTest.java
@@ -26,7 +26,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/VibrationAttributesTest.java b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
index f5a81c58..d8142c8 100644
--- a/core/tests/coretests/src/android/os/VibrationAttributesTest.java
+++ b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/WakeLockStatsTest.java b/core/tests/coretests/src/android/os/WakeLockStatsTest.java
index f3b18c8..2e842b2 100644
--- a/core/tests/coretests/src/android/os/WakeLockStatsTest.java
+++ b/core/tests/coretests/src/android/os/WakeLockStatsTest.java
@@ -18,7 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
index fcdc590..58a434a 100644
--- a/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
+++ b/core/tests/coretests/src/android/os/WorkDurationUnitTest.java
@@ -25,7 +25,7 @@
import android.platform.test.flag.junit.RavenwoodFlagsValueProvider;
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index 85dc127..d3f4f04 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -25,7 +25,7 @@
import android.os.WorkSource.WorkChain;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/CredentialManagementAppTest.java b/core/tests/coretests/src/android/security/CredentialManagementAppTest.java
index fa824b1..1690741 100644
--- a/core/tests/coretests/src/android/security/CredentialManagementAppTest.java
+++ b/core/tests/coretests/src/android/security/CredentialManagementAppTest.java
@@ -23,8 +23,8 @@
import android.net.Uri;
import android.util.Xml;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java
index ce0bf30..938147c 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainProtectionParamsTest.java
@@ -21,8 +21,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java
index 37fe22f..242a273 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyChainSnapshotTest.java
@@ -21,8 +21,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.google.common.collect.Lists;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java b/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java
index 2b37b52..d310b76 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/KeyDerivationParamsTest.java
@@ -21,8 +21,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java b/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java
index 3b8f715..29001ae 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/RecoveryCertPathTest.java
@@ -21,8 +21,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java b/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java
index 2b15d73..7677c3c 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/TrustedRootCertificatesTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java b/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java
index aec54e1..07353f8 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/WrappedApplicationKeyTest.java
@@ -21,8 +21,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java b/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java
index ba5e74a..7f91d03 100644
--- a/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java
+++ b/core/tests/coretests/src/android/security/keystore/recovery/X509CertificateParsingUtilsTest.java
@@ -21,8 +21,8 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/BidiFormatterTest.java b/core/tests/coretests/src/android/text/BidiFormatterTest.java
index 312fb68..307c95b 100644
--- a/core/tests/coretests/src/android/text/BidiFormatterTest.java
+++ b/core/tests/coretests/src/android/text/BidiFormatterTest.java
@@ -20,8 +20,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java b/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
index cca1ad3..81c4982 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutBlocksTest.java
@@ -23,8 +23,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
index 5939c06..5ff659b 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutOffsetMappingTest.java
@@ -23,8 +23,8 @@
import android.platform.test.annotations.Presubmit;
import android.text.method.OffsetMapping;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/DynamicLayoutTest.java b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
index 699243b..1036928 100644
--- a/core/tests/coretests/src/android/text/DynamicLayoutTest.java
+++ b/core/tests/coretests/src/android/text/DynamicLayoutTest.java
@@ -30,8 +30,8 @@
import android.text.style.ReplacementSpan;
import android.util.ArraySet;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/EmojiConsistencyTest.java b/core/tests/coretests/src/android/text/EmojiConsistencyTest.java
index c6e9e9c..72d09c8 100644
--- a/core/tests/coretests/src/android/text/EmojiConsistencyTest.java
+++ b/core/tests/coretests/src/android/text/EmojiConsistencyTest.java
@@ -18,8 +18,8 @@
import static junit.framework.Assert.assertEquals;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/EmojiTest.java b/core/tests/coretests/src/android/text/EmojiTest.java
index 0aeeb74..21f346e 100644
--- a/core/tests/coretests/src/android/text/EmojiTest.java
+++ b/core/tests/coretests/src/android/text/EmojiTest.java
@@ -22,8 +22,8 @@
import android.icu.lang.UCharacterDirection;
import android.icu.text.Bidi;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java b/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java
index 96e7fb9..7728866 100644
--- a/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java
+++ b/core/tests/coretests/src/android/text/LayoutBidiCursorPathTest.java
@@ -26,8 +26,8 @@
import android.view.KeyEvent;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index 98f8b7f..25f9cb7c 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -42,8 +42,8 @@
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.google.common.truth.Expect;
diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index 02b67e2..921a6bd8 100644
--- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -26,8 +26,8 @@
import android.graphics.text.MeasuredText;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/PackedIntVectorTest.java b/core/tests/coretests/src/android/text/PackedIntVectorTest.java
index ba15b92..e8d706d 100644
--- a/core/tests/coretests/src/android/text/PackedIntVectorTest.java
+++ b/core/tests/coretests/src/android/text/PackedIntVectorTest.java
@@ -20,8 +20,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpanColorsTest.java b/core/tests/coretests/src/android/text/SpanColorsTest.java
index 3d8d8f9..d2cb8c1 100644
--- a/core/tests/coretests/src/android/text/SpanColorsTest.java
+++ b/core/tests/coretests/src/android/text/SpanColorsTest.java
@@ -25,8 +25,8 @@
import android.text.style.ImageSpan;
import android.text.style.UnderlineSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
index 91b8c6a..b725133 100644
--- a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java
@@ -25,8 +25,8 @@
import android.text.style.SubscriptSpan;
import android.text.style.UnderlineSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
index 9149f7b..a2952f6 100644
--- a/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannableStringNoCopyTest.java
@@ -24,8 +24,8 @@
import android.text.style.QuoteSpan;
import android.text.style.UnderlineSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannableTest.java b/core/tests/coretests/src/android/text/SpannableTest.java
index d248a1f6..a3e6a78 100644
--- a/core/tests/coretests/src/android/text/SpannableTest.java
+++ b/core/tests/coretests/src/android/text/SpannableTest.java
@@ -21,8 +21,8 @@
import android.platform.test.annotations.Presubmit;
import android.test.MoreAsserts;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
index ca43733..3e2516f 100644
--- a/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
+++ b/core/tests/coretests/src/android/text/SpannedStringNoCopyTest.java
@@ -24,8 +24,8 @@
import android.text.style.QuoteSpan;
import android.text.style.UnderlineSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/SpannedTest.java b/core/tests/coretests/src/android/text/SpannedTest.java
index 3ab0755..e9a357c 100644
--- a/core/tests/coretests/src/android/text/SpannedTest.java
+++ b/core/tests/coretests/src/android/text/SpannedTest.java
@@ -26,8 +26,8 @@
import android.text.style.TextAppearanceSpan;
import android.text.style.TypefaceSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
index 32370b3e..3deda8c 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java
@@ -21,8 +21,8 @@
import android.platform.test.annotations.Presubmit;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
index 4221ac2..bc7efe4 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutDirectionsTest.java
@@ -22,8 +22,8 @@
import android.text.Layout.Directions;
import android.text.StaticLayoutTest.LayoutBuilder;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index 0ebf03f..3541900 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -31,8 +31,8 @@
import android.text.style.LocaleSpan;
import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java b/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java
index 0d42326..b32e94a 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTextMeasuringTest.java
@@ -21,8 +21,8 @@
import android.text.Layout.Alignment;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/TextLayoutTest.java b/core/tests/coretests/src/android/text/TextLayoutTest.java
index 15fbc9e..1584bc3 100644
--- a/core/tests/coretests/src/android/text/TextLayoutTest.java
+++ b/core/tests/coretests/src/android/text/TextLayoutTest.java
@@ -18,8 +18,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/TextLineTest.java b/core/tests/coretests/src/android/text/TextLineTest.java
index 8ae5669..2997853 100644
--- a/core/tests/coretests/src/android/text/TextLineTest.java
+++ b/core/tests/coretests/src/android/text/TextLineTest.java
@@ -30,10 +30,10 @@
import android.text.style.ReplacementSpan;
import android.text.style.TabStopSpan;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/TextShaperTest.java b/core/tests/coretests/src/android/text/TextShaperTest.java
index 77b14e6..84112ae 100644
--- a/core/tests/coretests/src/android/text/TextShaperTest.java
+++ b/core/tests/coretests/src/android/text/TextShaperTest.java
@@ -21,8 +21,8 @@
import android.graphics.fonts.Font;
import android.graphics.fonts.FontFileUtil;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index c4bcfd4..f552265 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -34,9 +34,9 @@
import android.text.util.Rfc822Tokenizer;
import android.view.View;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.google.android.collect.Lists;
diff --git a/core/tests/coretests/src/android/text/VariationParserTest.java b/core/tests/coretests/src/android/text/VariationParserTest.java
index 0afe811..8e93dd4 100644
--- a/core/tests/coretests/src/android/text/VariationParserTest.java
+++ b/core/tests/coretests/src/android/text/VariationParserTest.java
@@ -22,8 +22,8 @@
import android.graphics.fonts.FontVariationAxis;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 212cc44..59af6dd 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -25,8 +25,8 @@
import android.icu.text.DateFormatSymbols;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 9750de3..a07d399 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -42,8 +42,8 @@
import android.icu.util.ULocale;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 381c051..47be893 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -23,8 +23,8 @@
import android.os.LocaleList;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java
index 986cee5..555292e 100644
--- a/core/tests/coretests/src/android/text/format/FormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/FormatterTest.java
@@ -28,8 +28,8 @@
import android.text.format.Formatter.BytesResult;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
index 2337802..cd31950 100644
--- a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
@@ -36,8 +36,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java b/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
index b605520..c8cb5f3 100644
--- a/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/TimeMigrationUtilsTest.java
@@ -20,8 +20,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/text/format/TimeTest.java b/core/tests/coretests/src/android/text/format/TimeTest.java
index ac00411..6138ea1 100644
--- a/core/tests/coretests/src/android/text/format/TimeTest.java
+++ b/core/tests/coretests/src/android/text/format/TimeTest.java
@@ -24,9 +24,9 @@
import android.util.Log;
import android.util.TimeFormatException;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/method/BackspaceTest.java b/core/tests/coretests/src/android/text/method/BackspaceTest.java
index 19c2c61..a7ff244 100644
--- a/core/tests/coretests/src/android/text/method/BackspaceTest.java
+++ b/core/tests/coretests/src/android/text/method/BackspaceTest.java
@@ -24,8 +24,8 @@
import android.widget.TextView.BufferType;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
index 652622d..1e4024d 100644
--- a/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
+++ b/core/tests/coretests/src/android/text/method/ForwardDeleteTest.java
@@ -24,8 +24,8 @@
import android.widget.TextView.BufferType;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
index 9ef137b..2f336ab6 100644
--- a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
+++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
@@ -27,9 +27,8 @@
import android.view.View;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
import org.junit.BeforeClass;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/text/method/WordIteratorTest.java b/core/tests/coretests/src/android/text/method/WordIteratorTest.java
index cc345f5..046496a 100644
--- a/core/tests/coretests/src/android/text/method/WordIteratorTest.java
+++ b/core/tests/coretests/src/android/text/method/WordIteratorTest.java
@@ -23,8 +23,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java b/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java
index a0d2f85..043960d0 100644
--- a/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java
+++ b/core/tests/coretests/src/android/text/style/UnderlineSpanTest.java
@@ -24,8 +24,8 @@
import android.text.StaticLayout;
import android.text.TextPaint;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/text/util/LinkifyTest.java b/core/tests/coretests/src/android/text/util/LinkifyTest.java
index 107ecd7..52f3b2e 100644
--- a/core/tests/coretests/src/android/text/util/LinkifyTest.java
+++ b/core/tests/coretests/src/android/text/util/LinkifyTest.java
@@ -31,8 +31,8 @@
import android.widget.TextView;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/util/ArrayMapTest.java b/core/tests/coretests/src/android/util/ArrayMapTest.java
index 1e444ad..711ff94 100644
--- a/core/tests/coretests/src/android/util/ArrayMapTest.java
+++ b/core/tests/coretests/src/android/util/ArrayMapTest.java
@@ -19,8 +19,8 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/ArraySetTest.java b/core/tests/coretests/src/android/util/ArraySetTest.java
index 51de634..8888991 100644
--- a/core/tests/coretests/src/android/util/ArraySetTest.java
+++ b/core/tests/coretests/src/android/util/ArraySetTest.java
@@ -18,8 +18,8 @@
import static org.junit.Assert.fail;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/Base64Test.java b/core/tests/coretests/src/android/util/Base64Test.java
index b648266..3b322c2 100644
--- a/core/tests/coretests/src/android/util/Base64Test.java
+++ b/core/tests/coretests/src/android/util/Base64Test.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Ignore;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/BinaryXmlTest.java b/core/tests/coretests/src/android/util/BinaryXmlTest.java
index da29828..96c79013 100644
--- a/core/tests/coretests/src/android/util/BinaryXmlTest.java
+++ b/core/tests/coretests/src/android/util/BinaryXmlTest.java
@@ -30,7 +30,7 @@
import android.os.PersistableBundle;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/core/tests/coretests/src/android/util/CharsetUtilsTest.java b/core/tests/coretests/src/android/util/CharsetUtilsTest.java
index fbbe311..33936e9 100644
--- a/core/tests/coretests/src/android/util/CharsetUtilsTest.java
+++ b/core/tests/coretests/src/android/util/CharsetUtilsTest.java
@@ -21,7 +21,7 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.util.HexDump;
diff --git a/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java b/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java
index 72bd578..4587f5b 100644
--- a/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java
+++ b/core/tests/coretests/src/android/util/DayOfMonthCursorTest.java
@@ -21,8 +21,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/FloatMathTest.java b/core/tests/coretests/src/android/util/FloatMathTest.java
index f748acd..a52c9ac 100644
--- a/core/tests/coretests/src/android/util/FloatMathTest.java
+++ b/core/tests/coretests/src/android/util/FloatMathTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/KeyValueListParserTest.java b/core/tests/coretests/src/android/util/KeyValueListParserTest.java
index f65c4c7..eaf8d19 100644
--- a/core/tests/coretests/src/android/util/KeyValueListParserTest.java
+++ b/core/tests/coretests/src/android/util/KeyValueListParserTest.java
@@ -20,8 +20,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/LogNullabilityTest.java b/core/tests/coretests/src/android/util/LogNullabilityTest.java
index 475e347..5aa2626 100644
--- a/core/tests/coretests/src/android/util/LogNullabilityTest.java
+++ b/core/tests/coretests/src/android/util/LogNullabilityTest.java
@@ -21,8 +21,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/LogTest.java b/core/tests/coretests/src/android/util/LogTest.java
index 15caac9..0e44e68 100644
--- a/core/tests/coretests/src/android/util/LogTest.java
+++ b/core/tests/coretests/src/android/util/LogTest.java
@@ -19,8 +19,8 @@
import android.os.SystemProperties;
import android.test.PerformanceTestCase;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.Suppress;
-import androidx.test.runner.AndroidJUnit4;
import junit.framework.TestCase;
diff --git a/core/tests/coretests/src/android/util/LogWriterTest.java b/core/tests/coretests/src/android/util/LogWriterTest.java
index 890a401..739fbcd 100644
--- a/core/tests/coretests/src/android/util/LogWriterTest.java
+++ b/core/tests/coretests/src/android/util/LogWriterTest.java
@@ -16,7 +16,7 @@
package android.util;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/LongArrayQueueTest.java b/core/tests/coretests/src/android/util/LongArrayQueueTest.java
index 77e8d60..f696f70 100644
--- a/core/tests/coretests/src/android/util/LongArrayQueueTest.java
+++ b/core/tests/coretests/src/android/util/LongArrayQueueTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
index 9dbaae0d..fb0b2e3 100644
--- a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
+++ b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
@@ -18,8 +18,8 @@
import static org.junit.Assert.assertEquals;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/LruCacheTest.java b/core/tests/coretests/src/android/util/LruCacheTest.java
index 10e8308..1c6dcdf 100644
--- a/core/tests/coretests/src/android/util/LruCacheTest.java
+++ b/core/tests/coretests/src/android/util/LruCacheTest.java
@@ -20,7 +20,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java b/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java
index 06f970f..cb34c98 100644
--- a/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java
+++ b/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/MutableTest.java b/core/tests/coretests/src/android/util/MutableTest.java
index dfdff4d..1f73c16 100644
--- a/core/tests/coretests/src/android/util/MutableTest.java
+++ b/core/tests/coretests/src/android/util/MutableTest.java
@@ -20,7 +20,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/PatternsTest.java b/core/tests/coretests/src/android/util/PatternsTest.java
index a180ec3..8d0785f 100644
--- a/core/tests/coretests/src/android/util/PatternsTest.java
+++ b/core/tests/coretests/src/android/util/PatternsTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/PoolsTest.java b/core/tests/coretests/src/android/util/PoolsTest.java
index bdbc9b1..e31ab78 100644
--- a/core/tests/coretests/src/android/util/PoolsTest.java
+++ b/core/tests/coretests/src/android/util/PoolsTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/PrefixPrinterTest.java b/core/tests/coretests/src/android/util/PrefixPrinterTest.java
index a8d48ee..8199155 100644
--- a/core/tests/coretests/src/android/util/PrefixPrinterTest.java
+++ b/core/tests/coretests/src/android/util/PrefixPrinterTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/RecurrenceRuleTest.java b/core/tests/coretests/src/android/util/RecurrenceRuleTest.java
index 32548b4..8b2068c 100644
--- a/core/tests/coretests/src/android/util/RecurrenceRuleTest.java
+++ b/core/tests/coretests/src/android/util/RecurrenceRuleTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/util/SequenceUtilsTest.java b/core/tests/coretests/src/android/util/SequenceUtilsTest.java
index 6ca1751..e5ee04c 100644
--- a/core/tests/coretests/src/android/util/SequenceUtilsTest.java
+++ b/core/tests/coretests/src/android/util/SequenceUtilsTest.java
@@ -29,8 +29,8 @@
import android.platform.test.annotations.Presubmit;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/SingletonTest.java b/core/tests/coretests/src/android/util/SingletonTest.java
index 8c5a963..31ae650 100644
--- a/core/tests/coretests/src/android/util/SingletonTest.java
+++ b/core/tests/coretests/src/android/util/SingletonTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertTrue;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
index ba9c8d9..cc5ffbe 100644
--- a/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
+++ b/core/tests/coretests/src/android/util/SparseDoubleArrayTest.java
@@ -18,8 +18,8 @@
import static org.junit.Assert.assertEquals;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/SparseLongArrayTest.java b/core/tests/coretests/src/android/util/SparseLongArrayTest.java
index b29b6f1..4038d88 100644
--- a/core/tests/coretests/src/android/util/SparseLongArrayTest.java
+++ b/core/tests/coretests/src/android/util/SparseLongArrayTest.java
@@ -21,8 +21,8 @@
import android.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/SparseSetArrayTest.java b/core/tests/coretests/src/android/util/SparseSetArrayTest.java
index a8dce70..48ea3a1 100644
--- a/core/tests/coretests/src/android/util/SparseSetArrayTest.java
+++ b/core/tests/coretests/src/android/util/SparseSetArrayTest.java
@@ -19,8 +19,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/StateSetTest.java b/core/tests/coretests/src/android/util/StateSetTest.java
index dfd1523..14e4e20 100644
--- a/core/tests/coretests/src/android/util/StateSetTest.java
+++ b/core/tests/coretests/src/android/util/StateSetTest.java
@@ -22,8 +22,8 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/util/TeeWriterTest.java b/core/tests/coretests/src/android/util/TeeWriterTest.java
index c78376a..cc1c091 100644
--- a/core/tests/coretests/src/android/util/TeeWriterTest.java
+++ b/core/tests/coretests/src/android/util/TeeWriterTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/util/XmlTest.java b/core/tests/coretests/src/android/util/XmlTest.java
index 91ebc2a..540f1180 100644
--- a/core/tests/coretests/src/android/util/XmlTest.java
+++ b/core/tests/coretests/src/android/util/XmlTest.java
@@ -26,7 +26,7 @@
import android.os.PersistableBundle;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
diff --git a/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java b/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java
index 729a555..e74027e 100644
--- a/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java
+++ b/core/tests/coretests/src/android/view/CompositionSamplingListenerTest.java
@@ -21,8 +21,8 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
index 7872810..0fdc239 100644
--- a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
+++ b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
@@ -23,8 +23,8 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index 0d1dde3..2c66330 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -44,8 +44,8 @@
import android.platform.test.annotations.Presubmit;
import android.view.DisplayCutout.ParcelableWrapper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/DisplayShapeTest.java b/core/tests/coretests/src/android/view/DisplayShapeTest.java
index 77dd8bd..7778ba1 100644
--- a/core/tests/coretests/src/android/view/DisplayShapeTest.java
+++ b/core/tests/coretests/src/android/view/DisplayShapeTest.java
@@ -27,8 +27,8 @@
import android.graphics.RectF;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 1682135..668487d 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -42,7 +42,7 @@
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.animation.LinearInterpolator;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index 61e05da..c3bd065 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -32,7 +32,7 @@
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 16bd20a..1144ee1 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.InsetsSource.ID_IME;
import static android.view.InsetsSource.SIDE_BOTTOM;
import static android.view.InsetsSource.SIDE_TOP;
@@ -54,12 +55,17 @@
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.SparseIntArray;
import android.view.WindowInsets.Type;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.window.flags.Flags;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -78,6 +84,9 @@
@RunWith(AndroidJUnit4.class)
public class InsetsStateTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int ID_STATUS_BAR = InsetsSource.createId(
null /* owner */, 0 /* index */, statusBars());
private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
@@ -854,4 +863,19 @@
);
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+ public void testCalculateInsets_forceConsumingCaptionBar() {
+ mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+ .setFrame(new Rect(0, 0, 100, 100))
+ .setVisible(true)
+ .setFlags(FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR);
+
+ final WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 1000, 1000), null, false,
+ SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
+ new SparseIntArray());
+
+ assertTrue(insets.isForceConsumingOpaqueCaptionBar());
+ }
}
diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java
index bad0485..d0f9a38 100644
--- a/core/tests/coretests/src/android/view/MotionEventTest.java
+++ b/core/tests/coretests/src/android/view/MotionEventTest.java
@@ -33,8 +33,8 @@
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
index b5b2d0c..8ac9292 100644
--- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
@@ -37,7 +37,7 @@
import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
import android.view.animation.LinearInterpolator;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/RoundedCornerTest.java b/core/tests/coretests/src/android/view/RoundedCornerTest.java
index 4349021..3992aa1 100644
--- a/core/tests/coretests/src/android/view/RoundedCornerTest.java
+++ b/core/tests/coretests/src/android/view/RoundedCornerTest.java
@@ -23,8 +23,8 @@
import android.graphics.Point;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/RoundedCornersTest.java b/core/tests/coretests/src/android/view/RoundedCornersTest.java
index ec665ad..c26d945 100644
--- a/core/tests/coretests/src/android/view/RoundedCornersTest.java
+++ b/core/tests/coretests/src/android/view/RoundedCornersTest.java
@@ -42,8 +42,8 @@
import android.util.DisplayMetrics;
import android.util.Pair;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
index 5f258949..bee5dc4 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
@@ -38,8 +38,8 @@
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
index dc43204..726ee85 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
@@ -35,8 +35,8 @@
import android.platform.test.annotations.Presubmit;
import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
index 71bdce4..5a5510c 100644
--- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
+++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
@@ -22,8 +22,8 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq;
@@ -33,7 +33,7 @@
import android.content.Context;
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.AfterClass;
import org.junit.BeforeClass;
diff --git a/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java b/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java
index 65dd34f..9ab14af 100644
--- a/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java
+++ b/core/tests/coretests/src/android/view/TunnelModeEnabledListenerTest.java
@@ -21,16 +21,16 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
@RunWith(AndroidJUnit4.class)
@MediumTest
@Presubmit
diff --git a/core/tests/coretests/src/android/view/ViewCaptureTest.java b/core/tests/coretests/src/android/view/ViewCaptureTest.java
index 218047c..9144cd6 100644
--- a/core/tests/coretests/src/android/view/ViewCaptureTest.java
+++ b/core/tests/coretests/src/android/view/ViewCaptureTest.java
@@ -26,9 +26,9 @@
import android.view.ViewDebug.HardwareCanvasProvider;
import android.view.ViewDebug.SoftwareCanvasProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 62291d4..18364ad 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -50,10 +50,10 @@
import android.widget.ProgressBar;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java b/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java
index 54524b2..f5c71f8 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTransientViewTest.java
@@ -25,9 +25,9 @@
import android.widget.FrameLayout;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/view/ViewInvalidateTest.java b/core/tests/coretests/src/android/view/ViewInvalidateTest.java
index c25a2deb..d4181d3 100644
--- a/core/tests/coretests/src/android/view/ViewInvalidateTest.java
+++ b/core/tests/coretests/src/android/view/ViewInvalidateTest.java
@@ -31,9 +31,9 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Assert;
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index ba1204b..c86045d 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -43,14 +43,14 @@
@Test
public void systemWindowInsets_afterConsuming_isConsumed() {
assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
- null, false, 0, 0, null, null, null, null,
+ null, false, 0, false, 0, null, null, null, null,
WindowInsets.Type.systemBars(), false, null, null, 0, 0)
.consumeSystemWindowInsets().isConsumed());
}
@Test
public void multiNullConstructor_isConsumed() {
- assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null,
+ assertTrue(new WindowInsets(null, null, null, false, 0, false, 0, null, null, null, null,
WindowInsets.Type.systemBars(), false, null, null, 0, 0).isConsumed());
}
@@ -67,7 +67,7 @@
WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0,
- 0, null, null, null, DisplayShape.NONE, systemBars(),
+ false, 0, null, null, null, DisplayShape.NONE, systemBars(),
true /* compatIgnoreVisibility */, null, null, 0, 0);
assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
}
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index dd8cc6e..e5ad561 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -42,8 +42,8 @@
import android.view.Display;
import android.view.View;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import com.google.common.base.Throwables;
@@ -1053,6 +1053,28 @@
assertFalse(mAccessibilityCache.isNodeInCache(childInfo));
}
+ @Test
+ public void getEventSourceClassName_windowStateChangedThenRemoved() {
+ final String sourceActivityClassName = "com.example.SomeActivity";
+ final AccessibilityEvent windowStateChangedEvent = new AccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ final View mockView = getMockViewWithA11yAndWindowIds(PARENT_VIEW_ID, WINDOW_ID_1);
+ windowStateChangedEvent.setSource(mockView);
+ windowStateChangedEvent.setClassName(sourceActivityClassName);
+
+ mAccessibilityCache.onAccessibilityEvent(windowStateChangedEvent);
+ assertEquals(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1),
+ sourceActivityClassName);
+
+ final AccessibilityEvent windowRemovedEvent = new AccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOWS_CHANGED);
+ windowRemovedEvent.setWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED);
+ windowRemovedEvent.setSource(mockView);
+
+ mAccessibilityCache.onAccessibilityEvent(windowRemovedEvent);
+ assertNull(mAccessibilityCache.getEventSourceClassName(WINDOW_ID_1));
+ }
+
private AccessibilityWindowInfo obtainAccessibilityWindowInfo(int windowId, int layer) {
AccessibilityWindowInfo windowInfo = AccessibilityWindowInfo.obtain();
windowInfo.setId(windowId);
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
index ddc27aa..3b8f66a 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityEventTest.java
@@ -23,7 +23,7 @@
import android.os.Parcel;
import android.view.Display;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index 3e061d2..eb482f2e 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -26,8 +26,8 @@
import android.os.Bundle;
import android.os.RemoteException;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import libcore.util.EmptyArray;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index ce36ee0..82e3427 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -55,7 +55,7 @@
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.accessibility.common.ShortcutConstants;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 2d82d23..a5137bdf 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -28,8 +28,8 @@
import android.util.ArraySet;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.CollectionUtils;
@@ -46,7 +46,7 @@
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 44;
+ private static final int NUM_MARSHALLED_PROPERTIES = 43;
/**
* The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java b/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java
index e1b403f..c5e0ebd 100644
--- a/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/ConversationActionTest.java
@@ -26,8 +26,8 @@
import android.os.Bundle;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java
index 46e3a4c..1c26da7 100644
--- a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java
@@ -20,8 +20,8 @@
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java b/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java
index e4cfc53..4635e9b 100644
--- a/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/SystemTextClassifierMetadataTest.java
@@ -20,11 +20,10 @@
import static org.testng.Assert.assertThrows;
-
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
index 20a8768..07fe12f 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
@@ -20,8 +20,8 @@
import android.provider.DeviceConfig;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
index 8225afc..2ed016c 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
@@ -38,8 +38,8 @@
import android.view.View;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java
index 11eb567..d92da6e 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierEventTest.java
@@ -19,8 +19,8 @@
import android.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java
index 011866d..ec46426 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassifierUtilsTest.java
@@ -20,8 +20,8 @@
import static org.testng.Assert.assertThrows;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java
index 31f8029..de6f1d2 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java
@@ -24,8 +24,8 @@
import android.os.Bundle;
import android.os.Parcel;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
index 4f0b44b..bd1f7e1 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
@@ -25,8 +25,8 @@
import android.os.Parcel;
import android.util.ArrayMap;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
index 14c077c..61e6738 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
@@ -36,8 +36,8 @@
import android.os.Parcel;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java b/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java
index ceea6ca..e36a7f1 100644
--- a/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/AbsListViewFunctionalTest.java
@@ -28,9 +28,9 @@
import android.util.AttributeSet;
import android.util.PollingCheck;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.WidgetTestUtils;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/AbsSeekBarTest.java b/core/tests/coretests/src/android/widget/AbsSeekBarTest.java
index ccd873d..451fcf4 100644
--- a/core/tests/coretests/src/android/widget/AbsSeekBarTest.java
+++ b/core/tests/coretests/src/android/widget/AbsSeekBarTest.java
@@ -31,9 +31,9 @@
import android.graphics.drawable.shapes.RectShape;
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/widget/AppWidgetHostViewTest.java b/core/tests/coretests/src/android/widget/AppWidgetHostViewTest.java
index 6edc162..6a8b426 100644
--- a/core/tests/coretests/src/android/widget/AppWidgetHostViewTest.java
+++ b/core/tests/coretests/src/android/widget/AppWidgetHostViewTest.java
@@ -28,8 +28,8 @@
import android.view.ViewGroup.OnHierarchyChangeListener;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/DateTimeViewTest.java b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
index 14b48ed..a8fd913 100644
--- a/core/tests/coretests/src/android/widget/DateTimeViewTest.java
+++ b/core/tests/coretests/src/android/widget/DateTimeViewTest.java
@@ -21,8 +21,8 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index c37a34a..8f52d23 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -48,10 +48,10 @@
import android.view.View;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.Suppress;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/EditorCursorTest.java b/core/tests/coretests/src/android/widget/EditorCursorTest.java
index 585c601..9513aa5 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorTest.java
@@ -34,9 +34,9 @@
import android.app.Instrumentation;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index 5d62f1c..981f1db 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -29,9 +29,9 @@
import android.util.AttributeSet;
import android.util.PollingCheck;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/NumberPickerTest.java b/core/tests/coretests/src/android/widget/NumberPickerTest.java
index cab7c89..386972a 100644
--- a/core/tests/coretests/src/android/widget/NumberPickerTest.java
+++ b/core/tests/coretests/src/android/widget/NumberPickerTest.java
@@ -24,9 +24,9 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/widget/ProgressBarTest.java b/core/tests/coretests/src/android/widget/ProgressBarTest.java
index 5b92471..fa6dd31 100644
--- a/core/tests/coretests/src/android/widget/ProgressBarTest.java
+++ b/core/tests/coretests/src/android/widget/ProgressBarTest.java
@@ -26,9 +26,9 @@
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
index 534420e..b021b62 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -41,8 +41,8 @@
import android.view.View;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
import com.android.internal.widget.IRemoteViewsFactory;
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsProtoTest.java b/core/tests/coretests/src/android/widget/RemoteViewsProtoTest.java
index 8e9ba7b..7c14032 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsProtoTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsProtoTest.java
@@ -26,8 +26,8 @@
import android.view.View;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index c8ea374..0621d82 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -34,9 +34,6 @@
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncTask;
@@ -53,8 +50,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
index 6d0bab5..3dfb3f5 100644
--- a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
@@ -30,9 +30,9 @@
import android.util.AttributeSet;
import android.util.PollingCheck;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index a0cfb31..3de4893 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -47,9 +47,9 @@
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 9cf2e42..3eb7d9a 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -88,10 +88,10 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.action.EspressoKey;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.Suppress;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
index 777246b..12f8c9c 100644
--- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
@@ -41,9 +41,9 @@
import android.view.textclassifier.TextClassification;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java b/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java
index a769ea4..ff99db3 100644
--- a/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewPerformanceTest.java
@@ -25,9 +25,9 @@
import android.view.ViewGroup;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java b/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java
index 91266f0..8043a66 100644
--- a/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewProcessTextTest.java
@@ -26,9 +26,9 @@
import android.widget.TextView.BufferType;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java b/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
index 98bf87b..b61d868 100644
--- a/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewReceiveContentTest.java
@@ -47,9 +47,9 @@
import android.view.inputmethod.InputContentInfo;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index f5582d0..769a440 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -49,9 +49,9 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 9f5ed29..37625e2 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -67,8 +67,8 @@
import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
index f01ac6f..8608f6c 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/AccessibilityTargetTest.java
@@ -27,7 +27,7 @@
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Flags;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.accessibility.common.ShortcutConstants;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 9cac312..5339d91 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -41,8 +41,8 @@
import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.accessibility.TestUtils;
import com.android.internal.accessibility.common.ShortcutConstants;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java
index 58ab92a..f37ec9b 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/util/AccessibilityUtilsTest.java
@@ -33,7 +33,7 @@
import android.text.SpannableString;
import android.text.style.LocaleSpan;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
index 4eccbe5..a466caf 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java
@@ -22,7 +22,7 @@
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.content.om.OverlayConfigParser;
diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
index a0e9947..43cff8d 100644
--- a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
+++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigTest.java
@@ -28,7 +28,7 @@
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.frameworks.coretests.R;
import com.android.internal.content.om.OverlayConfig;
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
index 8d825e4..7928e6a 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
@@ -26,8 +26,8 @@
import android.os.Build;
import android.os.ConditionVariable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.SystemUtil;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index b70f290..5f8ab28 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -35,8 +35,8 @@
import android.util.ArrayMap;
import android.util.SparseArray;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BinderInternal.CallSession;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
index 27398ea..66de3d7 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java
@@ -34,8 +34,8 @@
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java b/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java
index a1b80d2..13d72137 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderHeavyHitterTest.java
@@ -23,7 +23,7 @@
import android.os.Binder;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.os.BinderCallHeavyHitterWatcher.HeavyHitterContainer;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java
index b2054f1..c6baea3 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyBucketsTest.java
@@ -22,8 +22,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
index 31b55e6..3355cc3 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
@@ -29,8 +29,8 @@
import android.util.ArrayMap;
import android.util.proto.ProtoOutputStream;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BinderInternal.CallSession;
import com.android.internal.os.BinderLatencyObserver.LatencyDims;
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
index 5f02f04..9acd991 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderfsStatsReaderTest.java
@@ -23,8 +23,8 @@
import android.os.FileUtils;
import android.util.IntArray;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java
index 2da3873..ca97472 100644
--- a/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java
@@ -20,7 +20,7 @@
import android.os.FileUtils;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
index 1d8628d..8fd87c0 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcStringReaderTest.java
@@ -28,8 +28,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
index a57a400..78cf65c 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
@@ -28,8 +28,8 @@
import android.platform.test.annotations.Presubmit;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
index 7eac2a3..c3d4b83 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
@@ -30,8 +30,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
index 610e6ae..b75ad7f 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
@@ -28,16 +28,15 @@
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.runner.RunWith;
import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -50,7 +49,6 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
-import java.util.stream.IntStream;
@SmallTest
@RunWith(AndroidJUnit4.class)
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
index 6507226..864e198 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidUserSysTimeReaderTest.java
@@ -28,8 +28,8 @@
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java b/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java
index ad5186e..a74f339 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java
@@ -22,8 +22,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.LongSparseLongArray;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
index f42d26d..cdfef25 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleProcessCpuThreadReaderTest.java
@@ -22,8 +22,8 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
index 632dce0..0a0ad39 100644
--- a/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index fa5d72a..b86dc58 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -24,8 +24,8 @@
import android.os.Parcel;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
index 78ef92b..b8f7903 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongMultiStateCounterTest.java
@@ -24,8 +24,8 @@
import android.os.Parcel;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
index dfb5cc3..8bb0ee7 100644
--- a/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LooperStatsTest.java
@@ -25,8 +25,8 @@
import android.platform.test.annotations.Presubmit;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Assert;
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 7ffc7b2..cff57f4 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -20,8 +20,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
index 951fa98..30dd50e 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java
@@ -33,8 +33,8 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.Xml;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.power.ModemPowerProfile;
import com.android.internal.util.XmlUtils;
diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
index 4846ed27..046f747 100644
--- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java
@@ -26,8 +26,8 @@
import android.util.SparseArray;
import android.util.Xml;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
index f61fc7c..a05a44c5 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
@@ -21,8 +21,8 @@
import android.os.FileUtils;
import android.util.IntArray;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
index 3e4f34d..7c7e2ed 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java
@@ -20,8 +20,8 @@
import android.os.FileUtils;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
index a706350..93dd09db 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java
@@ -24,8 +24,8 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
index cc6c4e8..26f5955 100644
--- a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java
@@ -23,8 +23,8 @@
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
index a978e3b..7b9ea55 100644
--- a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
@@ -21,9 +21,9 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.coretests.R;
diff --git a/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java
index fdde36a..fdba811 100644
--- a/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/BitUtilsTest.java
@@ -30,8 +30,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java
index ac954d6a..32c2d63 100644
--- a/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/CollectionUtilsTest.java
@@ -22,7 +22,7 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
index e6ebfef..aa59afe 100644
--- a/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
@@ -33,8 +33,8 @@
import android.text.style.TextAppearanceSpan;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
index d2d3c13..7bd062a 100644
--- a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
@@ -31,7 +31,7 @@
import android.content.ComponentName;
import android.util.SparseArray;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java b/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java
index 61d4e3d..9259181 100644
--- a/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/DumpableContainerImplTest.java
@@ -19,7 +19,7 @@
import android.util.Dumpable;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.util.dump.DumpableContainerImpl;
diff --git a/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
index 6bd67ea..aee352b 100644
--- a/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java
@@ -28,7 +28,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.DeviceConfig;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
diff --git a/core/tests/coretests/src/com/android/internal/util/FastMathTest.java b/core/tests/coretests/src/com/android/internal/util/FastMathTest.java
index dd26334..bedcf4c 100644
--- a/core/tests/coretests/src/com/android/internal/util/FastMathTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/FastMathTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java
index 8456161..a0eb058 100644
--- a/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/GrowingArrayUtilsTest.java
@@ -23,7 +23,7 @@
import android.util.EmptyArray;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
index dcffa1c..9adf607 100644
--- a/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
@@ -22,7 +22,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/IntPairTest.java b/core/tests/coretests/src/com/android/internal/util/IntPairTest.java
index af6503f..527be8f 100644
--- a/core/tests/coretests/src/com/android/internal/util/IntPairTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/IntPairTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
index 010f724..ce265a3 100644
--- a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
@@ -29,7 +29,7 @@
import android.platform.test.ravenwood.RavenwoodRule;
import android.provider.DeviceConfig;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.util.LatencyTracker.ActionProperties;
diff --git a/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java
index e6418fa..93262f0 100644
--- a/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/LineBreakBufferedWriterTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
index d24cbfe..b22014e 100644
--- a/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/ParseUtilsTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java b/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java
index 0d21335..e0d5499 100644
--- a/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/ProgressReporterTest.java
@@ -21,7 +21,7 @@
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Rule;
diff --git a/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java b/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java
index d7a100a..4497770 100644
--- a/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/RingBufferTest.java
@@ -20,8 +20,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java b/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java
index efef7ff..dbd6fc1 100644
--- a/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/SizedInputStreamTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
index ef579fe..43ee3c5 100644
--- a/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/TokenBucketTest.java
@@ -24,7 +24,7 @@
import android.os.SystemClock;
import android.text.format.DateUtils;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index 2b8adcb..ad6fe8d 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -40,8 +40,8 @@
import android.widget.Toolbar;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 92a7d8e..00b4f46 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -19,10 +19,10 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
import static com.google.common.truth.Truth.assertThat;
@@ -54,8 +54,8 @@
import android.test.mock.MockContentResolver;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.FakeSettingsProvider;
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
index bf9221a..548911b 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
@@ -35,8 +35,8 @@
import android.widget.flags.Flags;
import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
import com.google.common.truth.Expect;
diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
index cfa12bb..6210a00 100644
--- a/core/tests/vibrator/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -222,6 +222,18 @@
}
@Test
+ public void vibrate_withVibrationAttributesAndReason_usesGivenAttributesAndReason() {
+ VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ VibrationAttributes attributes = new VibrationAttributes.Builder().setUsage(
+ VibrationAttributes.USAGE_TOUCH).build();
+ String reason = "reason";
+
+ mVibratorSpy.vibrate(effect, attributes, reason);
+
+ verify(mVibratorSpy).vibrate(anyInt(), anyString(), eq(effect), eq(reason), eq(attributes));
+ }
+
+ @Test
public void vibrate_withAudioAttributes_createsVibrationAttributesWithSameUsage() {
VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5d4139e..1fe6ca7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -505,6 +505,7 @@
<permission name="android.permission.RENOUNCE_PERMISSIONS" />
<permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
+ <permission name="android.permission.READ_DROPBOX_DATA" />
<permission name="android.permission.READ_LOGS" />
<permission name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
<permission name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index b83931f..df95a91 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -35,7 +35,6 @@
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
-import android.text.ClientFlags;
import android.text.GraphicsOperations;
import android.text.SpannableString;
import android.text.SpannedString;
@@ -1541,21 +1540,8 @@
* @return typeface
*/
public Typeface setTypeface(Typeface typeface) {
- return setTypefaceInternal(typeface, true);
- }
-
- private Typeface setTypefaceInternal(Typeface typeface, boolean clearFontVariationSettings) {
final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
nSetTypeface(mNativePaint, typefaceNative);
-
- if (ClientFlags.clearFontVariationSettings()) {
- if (clearFontVariationSettings && !Objects.equals(mTypeface, typeface)) {
- // We cannot call setFontVariationSetting with empty string or null because it calls
- // setTypeface method. To avoid recursive setTypeface call, manually resetting
- // mFontVariationSettings.
- mFontVariationSettings = null;
- }
- }
mTypeface = typeface;
return typeface;
}
@@ -2051,14 +2037,6 @@
* </li>
* </ul>
*
- * Note: This method replaces the Typeface previously set to this instance.
- * Until API {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, any caller of
- * {@link #setTypeface(Typeface)} should call this method with empty settings, then call
- * {@link #setTypeface(Typeface)}, then call this method with preferred variation settings.
- * The device API more than {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, the
- * {@link #setTypeface(Typeface)} method clears font variation settings. So caller of
- * {@link #setTypeface(Typeface)} should call this method again for applying variation settings.
- *
* @param fontVariationSettings font variation settings. You can pass null or empty string as
* no variation settings.
*
@@ -2081,8 +2059,8 @@
if (settings == null || settings.length() == 0) {
mFontVariationSettings = null;
- setTypefaceInternal(Typeface.createFromTypefaceWithVariation(mTypeface,
- Collections.emptyList()), false);
+ setTypeface(Typeface.createFromTypefaceWithVariation(mTypeface,
+ Collections.emptyList()));
return true;
}
@@ -2100,8 +2078,7 @@
return false;
}
mFontVariationSettings = settings;
- setTypefaceInternal(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes),
- false);
+ setTypeface(Typeface.createFromTypefaceWithVariation(targetTypeface, filteredAxes));
return true;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 544f0f3..882a8d0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -565,6 +565,7 @@
return true;
}
+ // Only called by onTouch() and mRenderer is already null-checked.
@GuardedBy("mLock")
private void onStartDragging(@NonNull MotionEvent event) {
mVelocityTracker = VelocityTracker.obtain();
@@ -590,6 +591,7 @@
});
}
+ // Only called by onTouch() and mRenderer is already null-checked.
@GuardedBy("mLock")
private void onDrag(@NonNull MotionEvent event) {
if (mVelocityTracker != null) {
@@ -660,8 +662,10 @@
@GuardedBy("mLock")
private void updateDividerPosition(int position) {
- mRenderer.setDividerPosition(position);
- mRenderer.updateSurface();
+ if (mRenderer != null) {
+ mRenderer.setDividerPosition(position);
+ mRenderer.updateSurface();
+ }
}
@GuardedBy("mLock")
@@ -669,7 +673,10 @@
// Veil visibility change should be applied together with the surface boost transaction in
// the wct.
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mRenderer.hideVeils(t);
+
+ if (mRenderer != null) {
+ mRenderer.hideVeils(t);
+ }
// Callbacks must be executed on the executor to release mLock and prevent deadlocks.
// mDecorSurfaceOwner may change between here and when the callback is executed,
@@ -684,8 +691,10 @@
}
});
});
- mRenderer.mIsDragging = false;
- mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+ if (mRenderer != null) {
+ mRenderer.mIsDragging = false;
+ mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+ }
}
/**
@@ -1090,13 +1099,14 @@
@NonNull
private final SurfaceControl mDividerSurface;
@NonNull
+ private final SurfaceControl mDividerLineSurface;
+ @NonNull
private final WindowlessWindowManager mWindowlessWindowManager;
@NonNull
private final SurfaceControlViewHost mViewHost;
@NonNull
private final FrameLayout mDividerLayout;
- @NonNull
- private final View mDividerLine;
+ @Nullable
private View mDragHandle;
@NonNull
private final View.OnTouchListener mListener;
@@ -1115,7 +1125,10 @@
mProperties = properties;
mListener = listener;
- mDividerSurface = createChildSurface("DividerSurface", true /* visible */);
+ mDividerSurface = createChildSurface(
+ mProperties.mDecorSurface, "DividerSurface", true /* visible */);
+ mDividerLineSurface = createChildSurface(
+ mDividerSurface, "DividerLineSurface", true /* visible */);
mWindowlessWindowManager = new WindowlessWindowManager(
mProperties.mConfiguration,
mDividerSurface,
@@ -1127,7 +1140,6 @@
context, displayManager.getDisplay(mProperties.mDisplayId),
mWindowlessWindowManager, "DividerContainer");
mDividerLayout = new FrameLayout(context);
- mDividerLine = new View(context);
update();
}
@@ -1220,6 +1232,7 @@
dividerSurfacePosition = mDividerPosition;
}
+ // Update the divider surface position relative to the decor surface
if (mProperties.mIsVerticalSplit) {
t.setPosition(mDividerSurface, dividerSurfacePosition, 0.0f);
t.setWindowCrop(mDividerSurface, mDividerSurfaceWidthPx, taskBounds.height());
@@ -1228,10 +1241,24 @@
t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerSurfaceWidthPx);
}
- // Update divider line position in the surface
+ // Update divider line surface position relative to the divider surface
final int offset = mDividerPosition - dividerSurfacePosition;
- mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0);
- mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset);
+ if (mProperties.mIsVerticalSplit) {
+ t.setPosition(mDividerLineSurface, offset, 0);
+ t.setWindowCrop(mDividerLineSurface,
+ mProperties.mDividerWidthPx, taskBounds.height());
+ } else {
+ t.setPosition(mDividerLineSurface, 0, offset);
+ t.setWindowCrop(mDividerLineSurface,
+ taskBounds.width(), mProperties.mDividerWidthPx);
+ }
+
+ // Update divider line surface visibility and color.
+ // If a container is fully expanded, the divider line is invisible unless dragging.
+ final boolean isDividerLineVisible = !mProperties.mIsDraggableExpandType || mIsDragging;
+ t.setVisibility(mDividerLineSurface, isDividerLineVisible);
+ t.setColor(mDividerLineSurface, colorToFloatArray(
+ Color.valueOf(mProperties.mDividerAttributes.getDividerColor())));
if (mIsDragging) {
updateVeils(t);
@@ -1277,21 +1304,6 @@
*/
private void updateDivider(@NonNull SurfaceControl.Transaction t) {
mDividerLayout.removeAllViews();
- mDividerLayout.addView(mDividerLine);
- if (mProperties.mIsDraggableExpandType && !mIsDragging) {
- // If a container is fully expanded, the divider overlays on the expanded container.
- mDividerLine.setBackgroundColor(Color.TRANSPARENT);
- } else {
- mDividerLine.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
- }
- final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
- mDividerLine.setLayoutParams(
- mProperties.mIsVerticalSplit
- ? new FrameLayout.LayoutParams(
- mProperties.mDividerWidthPx, taskBounds.height())
- : new FrameLayout.LayoutParams(
- taskBounds.width(), mProperties.mDividerWidthPx)
- );
if (mProperties.mDividerAttributes.getDividerType()
== DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
createVeils();
@@ -1345,10 +1357,11 @@
}
@NonNull
- private SurfaceControl createChildSurface(@NonNull String name, boolean visible) {
+ private SurfaceControl createChildSurface(
+ @NonNull SurfaceControl parent, @NonNull String name, boolean visible) {
final Rect bounds = mProperties.mConfiguration.windowConfiguration.getBounds();
return new SurfaceControl.Builder()
- .setParent(mProperties.mDecorSurface)
+ .setParent(parent)
.setName(name)
.setHidden(!visible)
.setCallsite("DividerManager.createChildSurface")
@@ -1359,10 +1372,12 @@
private void createVeils() {
if (mPrimaryVeil == null) {
- mPrimaryVeil = createChildSurface("DividerPrimaryVeil", false /* visible */);
+ mPrimaryVeil = createChildSurface(
+ mProperties.mDecorSurface, "DividerPrimaryVeil", false /* visible */);
}
if (mSecondaryVeil == null) {
- mSecondaryVeil = createChildSurface("DividerSecondaryVeil", false /* visible */);
+ mSecondaryVeil = createChildSurface(
+ mProperties.mDecorSurface, "DividerSecondaryVeil", false /* visible */);
}
}
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 864f7cd..49d9029 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -17,6 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/handle_menu"
android:layout_width="@dimen/desktop_mode_handle_menu_width"
android:layout_height="wrap_content"
android:clipChildren="false"
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 4e7cfb6..8669af3 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -263,7 +263,7 @@
<!-- Accessibility text for the caption back button [CHAR LIMIT=NONE] -->
<string name="back_button_text">Back</string>
<!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
- <string name="handle_text">Handle</string>
+ <string name="handle_text">App handle</string>
<!-- Accessibility text for the handle menu app icon [CHAR LIMIT=NONE] -->
<string name="app_icon_text">App Icon</string>
<!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
index b1c9a77..2b01eac 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt
@@ -35,6 +35,7 @@
) {
// All desktop mode related flags will be added here
DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true),
+ CASCADING_WINDOWS(Flags::enableCascadingWindows, true),
WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity, true),
MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true),
THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index 8d30db6..86e0f14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -146,6 +146,11 @@
/** To be overridden by subclasses to adjust the animation surface change. */
void onAnimationUpdateInner(@NonNull SurfaceControl.Transaction t) {
// Update the surface position and alpha.
+ if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()
+ && mAnimation.getExtensionEdges() != 0) {
+ t.setEdgeExtensionEffect(mLeash, mAnimation.getExtensionEdges());
+ }
+
mTransformation.getMatrix().postTranslate(mContentRelOffset.x, mContentRelOffset.y);
t.setMatrix(mLeash, mTransformation.getMatrix(), mMatrix);
t.setAlpha(mLeash, mTransformation.getAlpha());
@@ -165,7 +170,7 @@
if (!cropRect.intersect(mWholeAnimationBounds)) {
// Hide the surface when it is outside of the animation area.
t.setAlpha(mLeash, 0);
- } else if (mAnimation.hasExtension()) {
+ } else if (mAnimation.getExtensionEdges() != 0) {
// Allow the surface to be shown in its original bounds in case we want to use edge
// extensions.
cropRect.union(mContentBounds);
@@ -180,6 +185,7 @@
@CallSuper
void onAnimationEnd(@NonNull SurfaceControl.Transaction t) {
onAnimationUpdate(t, mAnimation.getDuration());
+ t.setEdgeExtensionEffect(mLeash, /* edge */ 0);
}
final long getDurationHint() {
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 5696a54..d2cef4b 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
@@ -144,8 +144,10 @@
// ending states.
prepareForJumpCut(info, startTransaction);
} else {
- addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
- postStartTransactionCallbacks, adapters);
+ if (!com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+ addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
+ postStartTransactionCallbacks, adapters);
+ }
addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
duration = Math.max(duration, adapter.getDurationHint());
@@ -341,7 +343,7 @@
@NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
final Animation animation = adapter.mAnimation;
- if (!animation.hasExtension()) {
+ if (animation.getExtensionEdges() == 0) {
continue;
}
if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index d54a6b0..c91567d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -80,6 +80,7 @@
outline.setPath(mPath);
}
});
+ setContentDescription(getResources().getString(R.string.handle_text));
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 4fbb574..d7d19f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -315,7 +315,7 @@
}
}
if (!mImeShowing) {
- removeImeSurface();
+ removeImeSurface(mDisplayId);
}
}
} else if (!android.view.inputmethod.Flags.refactorInsetsController()
@@ -617,7 +617,7 @@
|| hasLeash) {
t.hide(mImeSourceControl.getLeash());
}
- removeImeSurface();
+ removeImeSurface(mDisplayId);
ImeTracker.forLogging().onHidden(mStatsToken);
} else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
ImeTracker.forLogging().onShown(mStatsToken);
@@ -671,10 +671,10 @@
}
}
- void removeImeSurface() {
+ void removeImeSurface(int displayId) {
// Remove the IME surface to make the insets invisible for
// non-client controlled insets.
- InputMethodManagerGlobal.removeImeSurface(
+ InputMethodManagerGlobal.removeImeSurface(displayId,
e -> Slog.e(TAG, "Failed to remove IME surface.", e));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ImeListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ImeListener.kt
new file mode 100644
index 0000000..a34d7bed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ImeListener.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.common
+
+import android.graphics.Rect
+import android.view.InsetsSource
+import android.view.InsetsState
+import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener
+
+abstract class ImeListener(
+ private val mDisplayController: DisplayController,
+ private val mDisplayId: Int
+) : OnInsetsChangedListener {
+ // The last insets state
+ private val mInsetsState = InsetsState()
+ private val mTmpBounds = Rect()
+
+ override fun insetsChanged(insetsState: InsetsState) {
+ if (mInsetsState == insetsState) {
+ return
+ }
+
+ // Get the stable bounds that account for display cutout and system bars to calculate the
+ // relative IME height
+ val layout = mDisplayController.getDisplayLayout(mDisplayId)
+ if (layout == null) {
+ return
+ }
+ layout.getStableBounds(mTmpBounds)
+
+ val wasVisible = getImeVisibilityAndHeight(mInsetsState).first
+ val oldHeight = getImeVisibilityAndHeight(mInsetsState).second
+
+ val isVisible = getImeVisibilityAndHeight(insetsState).first
+ val newHeight = getImeVisibilityAndHeight(insetsState).second
+
+ mInsetsState.set(insetsState, true)
+ if (wasVisible != isVisible || oldHeight != newHeight) {
+ onImeVisibilityChanged(isVisible, newHeight)
+ }
+ }
+
+ private fun getImeVisibilityAndHeight(
+ insetsState: InsetsState): Pair<Boolean, Int> {
+ val source = insetsState.peekSource(InsetsSource.ID_IME)
+ val frame = if (source != null && source.isVisible) source.frame else null
+ val height = if (frame != null) mTmpBounds.bottom - frame.top else 0
+ val visible = source?.isVisible ?: false
+ return Pair(visible, height)
+ }
+
+ /**
+ * To be overridden by implementations to handle IME changes.
+ */
+ protected abstract fun onImeVisibilityChanged(imeVisible: Boolean, imeHeight: Int)
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 5097ed8..19a109e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -31,6 +31,7 @@
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -201,7 +202,7 @@
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
- boolean immediately, float[] veilColor) {
+ boolean immediately) {
if (mResizingIconView == null) {
return;
}
@@ -234,7 +235,7 @@
if (mBackgroundLeash == null) {
mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession);
- t.setColor(mBackgroundLeash, veilColor)
+ t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask))
.setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
}
@@ -245,7 +246,7 @@
mGapBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
GAP_BACKGROUND_SURFACE_NAME, mSurfaceSession);
// Fill up another side bounds area.
- t.setColor(mGapBackgroundLeash, veilColor)
+ t.setColor(mGapBackgroundLeash, getResizingBackgroundColor(resizingTask))
.setLayer(mGapBackgroundLeash, Integer.MAX_VALUE - 2)
.setPosition(mGapBackgroundLeash, left, top)
.setWindowCrop(mGapBackgroundLeash, sideBounds.width(), sideBounds.height());
@@ -486,4 +487,9 @@
mIcon = null;
}
}
+
+ private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
+ return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents();
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index e8226051..f9259e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.common.split;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED;
+
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -24,18 +26,25 @@
import android.app.ActivityManager;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Color;
import android.graphics.Rect;
+import android.os.UserHandle;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.util.ArrayUtils;
import com.android.wm.shell.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
+import java.util.Arrays;
+import java.util.List;
+
/** Helper utility class for split screen components to use. */
public class SplitScreenUtils {
/** Reverse the split position. */
@@ -128,10 +137,4 @@
return isLandscape;
}
}
-
- /** Returns the specified background color that matches a RunningTaskInfo. */
- public static Color getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
- final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
- return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor);
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index f32683d..d289ef2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -95,7 +95,7 @@
mCallback = callback;
mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
if (DESKTOP_WINDOWING_MODE.isEnabled(mContext)
- && DesktopModeFlags.THEMED_APP_HEADERS.isEnabled(context)) {
+ && DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) {
// Don't show the SCM button for freeform tasks
mHasSizeCompat &= !taskInfo.isFreeform();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 247cc42..4299841 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -32,77 +32,77 @@
import java.util.concurrent.Executor
import java.util.function.Consumer
-/** Keeps track of task data related to desktop mode. */
+/** Tracks task data for Desktop Mode. */
class DesktopModeTaskRepository {
- /** Task data that is tracked per display */
- private data class DisplayData(
- /**
- * Set of task ids that are marked as active in desktop mode. Active tasks in desktop mode
- * are freeform tasks that are visible or have been visible after desktop mode was
- * activated. Task gets removed from this list when it vanishes. Or when desktop mode is
- * turned off.
- */
+ /**
+ * Task data tracked per desktop.
+ *
+ * @property activeTasks task ids of active tasks currently or previously visible in Desktop
+ * mode session. Tasks become inactive when task closes or when desktop mode session ends.
+ * @property visibleTasks task ids for active freeform tasks that are currently visible. There
+ * might be other active tasks in desktop mode that are not visible.
+ * @property minimizedTasks task ids for active freeform tasks that are currently minimized.
+ * @property closingTasks task ids for tasks that are going to close, but are currently visible.
+ * @property freeformTasksInZOrder list of current freeform task ids ordered from top to bottom
+ * (top is at index 0).
+ */
+ private data class DesktopTaskData(
val activeTasks: ArraySet<Int> = ArraySet(),
val visibleTasks: ArraySet<Int> = ArraySet(),
val minimizedTasks: ArraySet<Int> = ArraySet(),
- // Tasks that are closing, but are still visible
// TODO(b/332682201): Remove when the repository state is updated via TransitionObserver
val closingTasks: ArraySet<Int> = ArraySet(),
- // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
val freeformTasksInZOrder: ArrayList<Int> = ArrayList(),
)
- // Token of the current wallpaper activity, used to remove it when the last task is removed
+ /* Current wallpaper activity token to remove wallpaper activity when last task is removed. */
var wallpaperActivityToken: WindowContainerToken? = null
+
private val activeTasksListeners = ArraySet<ActiveTasksListener>()
- // Track visible tasks separately because a task may be part of the desktop but not visible.
private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
- // Track corner/caption regions of desktop tasks, used to determine gesture exclusion
+
+ /* Tracks corner/caption regions of desktop tasks, used to determine gesture exclusion. */
private val desktopExclusionRegions = SparseArray<Region>()
- // Track last bounds of task before toggled to stable bounds
+
+ /* Tracks last bounds of task before toggled to stable bounds. */
private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>()
+
private var desktopGestureExclusionListener: Consumer<Region>? = null
private var desktopGestureExclusionExecutor: Executor? = null
- private val displayData =
- object : SparseArray<DisplayData>() {
- /**
- * Get the [DisplayData] associated with this [displayId]
- *
- * Creates a new instance if one does not exist
- */
- fun getOrCreate(displayId: Int): DisplayData {
- if (!contains(displayId)) {
- put(displayId, DisplayData())
- }
- return get(displayId)
- }
- }
+ private val desktopTaskDataByDisplayId = object : SparseArray<DesktopTaskData>() {
+ /** Gets [DesktopTaskData] for existing [displayId] or creates a new one. */
+ fun getOrCreate(displayId: Int): DesktopTaskData =
+ this[displayId] ?: DesktopTaskData().also { this[displayId] = it }
+ }
- /** Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository. */
+ /** Adds [activeTasksListener] to be notified of updates to active tasks. */
fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) {
activeTasksListeners.add(activeTasksListener)
}
- /** Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not. */
+ /** Adds [visibleTasksListener] to be notified of updates to visible tasks. */
fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) {
visibleTasksListeners[visibleTasksListener] = executor
- displayData.keyIterator().forEach {
+ desktopTaskDataByDisplayId.keyIterator().forEach {
+ val visibleTaskCount = getVisibleTaskCount(it)
executor.execute {
- visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount(it))
+ visibleTasksListener.onTasksVisibilityChanged(it, visibleTaskCount)
}
}
}
- /** Returns a list of all [DisplayData]. */
- private fun displayDataList(): Sequence<DisplayData> =
- displayData.valueIterator().asSequence()
+ /** Updates tasks changes on all the active task listeners for given display id. */
+ private fun updateActiveTasksListeners(displayId: Int) {
+ activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
+ }
- /**
- * Add a Consumer which will inform other classes of changes to exclusion regions for all
- * Desktop tasks.
- */
+ /** Returns a list of all [DesktopTaskData] in the repository. */
+ private fun desktopTaskDataSequence(): Sequence<DesktopTaskData> =
+ desktopTaskDataByDisplayId.valueIterator().asSequence()
+
+ /** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
desktopGestureExclusionListener = regionListener
desktopGestureExclusionExecutor = executor
@@ -111,7 +111,7 @@
}
}
- /** Create a new merged region representative of all exclusion regions in all desktop tasks. */
+ /** Creates a new merged region representative of all exclusion regions in all desktop tasks. */
private fun calculateDesktopExclusionRegion(): Region {
val desktopExclusionRegion = Region()
desktopExclusionRegions.valueIterator().forEach { taskExclusionRegion ->
@@ -120,192 +120,120 @@
return desktopExclusionRegion
}
- /** Remove a previously registered [ActiveTasksListener] */
+ /** Remove the previously registered [activeTasksListener] */
fun removeActiveTasksListener(activeTasksListener: ActiveTasksListener) {
activeTasksListeners.remove(activeTasksListener)
}
- /** Remove a previously registered [VisibleTasksListener] */
+ /** Removes the previously registered [visibleTasksListener]. */
fun removeVisibleTasksListener(visibleTasksListener: VisibleTasksListener) {
visibleTasksListeners.remove(visibleTasksListener)
}
- /**
- * Mark a task with given [taskId] as active on given [displayId]
- *
- * @return `true` if the task was not active on given [displayId]
- */
- fun addActiveTask(displayId: Int, taskId: Int): Boolean {
- // Check if task is active on another display, if so, remove it
- displayData.forEach { id, data ->
- if (id != displayId && data.activeTasks.remove(taskId)) {
- activeTasksListeners.onEach { it.onActiveTasksChanged(id) }
+ /** Adds task with [taskId] to the list of active tasks on [displayId]. */
+ fun addActiveTask(displayId: Int, taskId: Int) {
+ // Removes task if it is active on another display excluding [displayId].
+ removeActiveTask(taskId, excludedDisplayId = displayId)
+
+ if (desktopTaskDataByDisplayId.getOrCreate(displayId).activeTasks.add(taskId)) {
+ logD("Adds active task=%d displayId=%d", taskId, displayId)
+ updateActiveTasksListeners(displayId)
+ }
+ }
+
+ /** Removes task from active task list of displays excluding the [excludedDisplayId]. */
+ fun removeActiveTask(taskId: Int, excludedDisplayId: Int? = null) {
+ desktopTaskDataByDisplayId.forEach { displayId, desktopTaskData ->
+ if ((displayId != excludedDisplayId)
+ && desktopTaskData.activeTasks.remove(taskId)) {
+ logD("Removed active task=%d displayId=%d", taskId, displayId)
+ updateActiveTasksListeners(displayId)
}
}
-
- val added = displayData.getOrCreate(displayId).activeTasks.add(taskId)
- if (added) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: add active task=%d displayId=%d",
- taskId,
- displayId
- )
- activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
- }
- return added
}
- /**
- * Remove task with given [taskId] from active tasks.
- *
- * @return `true` if the task was active
- */
- fun removeActiveTask(taskId: Int): Boolean {
- var result = false
- displayData.forEach { displayId, data ->
- if (data.activeTasks.remove(taskId)) {
- activeTasksListeners.onEach { it.onActiveTasksChanged(displayId) }
- result = true
+ /** Adds given task to the closing task list for [displayId]. */
+ fun addClosingTask(displayId: Int, taskId: Int) {
+ if (desktopTaskDataByDisplayId.getOrCreate(displayId).closingTasks.add(taskId)) {
+ logD("Added closing task=%d displayId=%d", taskId, displayId)
+ } else {
+ // If the task hasn't been removed from closing list after it disappeared.
+ logW("Task with taskId=%d displayId=%d is already closing", taskId, displayId)
+ }
+ }
+
+ /** Removes task from the list of closing tasks for [displayId]. */
+ fun removeClosingTask(taskId: Int) {
+ desktopTaskDataByDisplayId.forEach { displayId, taskInfo ->
+ if (taskInfo.closingTasks.remove(taskId)) {
+ logD("Removed closing task=%d displayId=%d", taskId, displayId)
}
}
- if (result) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove active task=%d", taskId)
- }
- return result
}
- /**
- * Mark a task with given [taskId] as closing on given [displayId]
- *
- * @return `true` if the task was not closing on given [displayId]
- */
- fun addClosingTask(displayId: Int, taskId: Int): Boolean {
- val added = displayData.getOrCreate(displayId).closingTasks.add(taskId)
- if (added) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: added closing task=%d displayId=%d",
- taskId,
- displayId
- )
- }
- return added
- }
+ fun isActiveTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.activeTasks }
+ fun isClosingTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.closingTasks }
+ fun isVisibleTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.visibleTasks }
+ fun isMinimizedTask(taskId: Int) = desktopTaskDataSequence().any { taskId in it.minimizedTasks }
- /**
- * Remove task with given [taskId] from closing tasks.
- *
- * @return `true` if the task was closing
- */
- fun removeClosingTask(taskId: Int): Boolean {
- var removed = false
- displayData.forEach { _, data ->
- if (data.closingTasks.remove(taskId)) {
- removed = true
- }
- }
- if (removed) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "DesktopTaskRepo: remove closing task=%d", taskId)
- }
- return removed
- }
-
- fun isActiveTask(taskId: Int) = displayDataList().any { taskId in it.activeTasks }
- fun isClosingTask(taskId: Int) = displayDataList().any { taskId in it.closingTasks }
- fun isVisibleTask(taskId: Int) = displayDataList().any { taskId in it.visibleTasks }
- fun isMinimizedTask(taskId: Int) = displayDataList().any { taskId in it.minimizedTasks }
-
- /**
- * Check if a task with the given [taskId] is the only visible, non-closing, not-minimized task
- * on its display
- */
+ /** Checks if a task is the only visible, non-closing, non-minimized task on its display. */
fun isOnlyVisibleNonClosingTask(taskId: Int): Boolean =
- displayDataList().any { data ->
- data.visibleTasks
- .subtract(data.closingTasks)
- .subtract(data.minimizedTasks)
- .singleOrNull() == taskId
+ desktopTaskDataSequence().any { it.visibleTasks
+ .subtract(it.closingTasks)
+ .subtract(it.minimizedTasks)
+ .singleOrNull() == taskId
}
- /** Get a set of the active tasks for given [displayId] */
- fun getActiveTasks(displayId: Int): ArraySet<Int> {
- return ArraySet(displayData[displayId]?.activeTasks)
- }
+ fun getActiveTasks(displayId: Int): ArraySet<Int> =
+ ArraySet(desktopTaskDataByDisplayId[displayId]?.activeTasks)
- /** Returns the minimized tasks for the given [displayId]. */
fun getMinimizedTasks(displayId: Int): ArraySet<Int> =
- ArraySet(displayData[displayId]?.minimizedTasks)
+ ArraySet(desktopTaskDataByDisplayId[displayId]?.minimizedTasks)
- /**
- * Returns a list of Tasks IDs representing all active non-minimized Tasks on the given display,
- * ordered from front to back.
- */
- fun getActiveNonMinimizedTasksOrderedFrontToBack(displayId: Int): List<Int> {
- val activeTasks = getActiveTasks(displayId)
- val allTasksInZOrder = getFreeformTasksInZOrder(displayId)
- return activeTasks
- // Don't show already minimized Tasks
- .filter { taskId -> !isMinimizedTask(taskId) }
- .sortedBy { taskId -> allTasksInZOrder.indexOf(taskId) }
+ /** Returns all active non-minimized tasks for [displayId] ordered from top to bottom. */
+ fun getActiveNonMinimizedOrderedTasks(displayId: Int): List<Int> =
+ getFreeformTasksInZOrder(displayId).filter { !isMinimizedTask(it) }
+
+ /** Returns a list of freeform tasks, ordered from top-bottom (top at index 0). */
+ fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
+ ArrayList(desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder ?: emptyList())
+
+ /** Removes task from visible tasks of all displays except [excludedDisplayId]. */
+ private fun removeVisibleTask(taskId: Int, excludedDisplayId: Int? = null) {
+ desktopTaskDataByDisplayId.forEach { displayId, data ->
+ if ((displayId != excludedDisplayId) && data.visibleTasks.remove(taskId)) {
+ notifyVisibleTaskListeners(displayId, data.visibleTasks.size)
+ }
+ }
}
- /** Get a list of freeform tasks, ordered from top-bottom (top at index 0). */
- fun getFreeformTasksInZOrder(displayId: Int): ArrayList<Int> =
- ArrayList(displayData[displayId]?.freeformTasksInZOrder ?: emptyList())
-
/**
- * Updates whether a freeform task with this id is visible or not and notifies listeners.
+ * Updates visibility of a freeform task with [taskId] on [displayId] and notifies listeners.
*
- * If the task was visible on a different display with a different displayId, it is removed from
- * the set of visible tasks on that display. Listeners will be notified.
+ * If task was visible on a different display with a different [displayId], removes from
+ * the set of visible tasks on that display and notifies listeners.
*/
fun updateVisibleFreeformTasks(displayId: Int, taskId: Int, visible: Boolean) {
if (visible) {
- // Task is visible. Check if we need to remove it from any other display.
- val otherDisplays = displayData.keyIterator().asSequence().filter { it != displayId }
- for (otherDisplayId in otherDisplays) {
- if (displayData[otherDisplayId].visibleTasks.remove(taskId)) {
- notifyVisibleTaskListeners(
- otherDisplayId,
- displayData[otherDisplayId].visibleTasks.size
- )
- }
- }
+ // If task is visible, remove it from any other display besides [displayId].
+ removeVisibleTask(taskId, excludedDisplayId = displayId)
} else if (displayId == INVALID_DISPLAY) {
// Task has vanished. Check which display to remove the task from.
- displayData.forEach { displayId, data ->
- if (data.visibleTasks.remove(taskId)) {
- notifyVisibleTaskListeners(displayId, data.visibleTasks.size)
- }
- }
+ removeVisibleTask(taskId)
return
}
-
- val prevCount = visibleTaskCount(displayId)
+ val prevCount = getVisibleTaskCount(displayId)
if (visible) {
- displayData.getOrCreate(displayId).visibleTasks.add(taskId)
+ desktopTaskDataByDisplayId.getOrCreate(displayId).visibleTasks.add(taskId)
unminimizeTask(displayId, taskId)
} else {
- displayData[displayId]?.visibleTasks?.remove(taskId)
+ desktopTaskDataByDisplayId[displayId]?.visibleTasks?.remove(taskId)
}
- val newCount = visibleTaskCount(displayId)
-
- // Check if count changed
+ val newCount = getVisibleTaskCount(displayId)
if (prevCount != newCount) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: update task visibility taskId=%d visible=%b displayId=%d",
- taskId,
- visible,
- displayId
- )
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: visibleTaskCount has changed from %d to %d",
- prevCount,
- newCount
- )
+ logD("Update task visibility taskId=%d visible=%b displayId=%d",
+ taskId, visible, displayId)
+ logD("VisibleTaskCount has changed from %d to %d", prevCount, newCount)
notifyVisibleTaskListeners(displayId, newCount)
}
}
@@ -316,72 +244,46 @@
}
}
- /** Get number of tasks that are marked as visible on given [displayId] */
- fun visibleTaskCount(displayId: Int): Int {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: visibleTaskCount= %d",
- displayData[displayId]?.visibleTasks?.size ?: 0
- )
- return displayData[displayId]?.visibleTasks?.size ?: 0
- }
+ /** Gets number of visible tasks on given [displayId] */
+ fun getVisibleTaskCount(displayId: Int): Int =
+ desktopTaskDataByDisplayId[displayId]?.visibleTasks?.size ?: 0.also {
+ logD("getVisibleTaskCount=$it")
+ }
- /** Add (or move if it already exists) the task to the top of the ordered list. */
- // TODO(b/342417921): Identify if there is additional checks needed to move tasks for
- // multi-display scenarios.
+ /** Adds task (or moves if it already exists) to the top of the ordered list. */
fun addOrMoveFreeformTaskToTop(displayId: Int, taskId: Int) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: add or move task to top: display=%d, taskId=%d",
- displayId,
- taskId
- )
- displayData[displayId]?.freeformTasksInZOrder?.remove(taskId)
- displayData.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
+ logD("Add or move task to top: display=%d taskId=%d", taskId, displayId)
+ desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
+ desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
}
- /** Mark a Task as minimized. */
+ /** Minimizes the task for [taskId] and [displayId] */
fun minimizeTask(displayId: Int, taskId: Int) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopModeTaskRepository: minimize Task: display=%d, task=%d",
- displayId,
- taskId
- )
- displayData.getOrCreate(displayId).minimizedTasks.add(taskId)
+ logD("Minimize Task: display=%d, task=%d", displayId, taskId)
+ desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
}
- /** Mark a Task as non-minimized. */
+ /** Unminimizes the task for [taskId] and [displayId] */
fun unminimizeTask(displayId: Int, taskId: Int) {
- ProtoLog.v(
- WM_SHELL_DESKTOP_MODE,
- "DesktopModeTaskRepository: unminimize Task: display=%d, task=%d",
- displayId,
- taskId
- )
- displayData[displayId]?.minimizedTasks?.remove(taskId)
+ logD("Unminimize Task: display=%d, task=%d", displayId, taskId)
+ desktopTaskDataByDisplayId[displayId]?.minimizedTasks?.remove(taskId) ?:
+ logW("Unminimize Task: display=%d, task=%d, no task data", displayId, taskId)
}
- /** Remove the task from the ordered list. */
+ /** Removes task from the ordered list. */
fun removeFreeformTask(displayId: Int, taskId: Int) {
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: remove freeform task from ordered list: display=%d, taskId=%d",
- displayId,
- taskId
- )
- displayData[displayId]?.freeformTasksInZOrder?.remove(taskId)
+ logD("Removes freeform task: taskId=%d", taskId)
+ desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.remove(taskId)
boundsBeforeMaximizeByTaskId.remove(taskId)
- ProtoLog.d(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTaskRepo: remaining freeform tasks: %s",
- displayData[displayId]?.freeformTasksInZOrder?.toDumpString() ?: ""
- )
+ logD("Remaining freeform tasks: %d",
+ desktopTaskDataByDisplayId[displayId]?.freeformTasksInZOrder?.toDumpString() ?: "")
}
/**
- * Updates the active desktop gesture exclusion regions; if desktopExclusionRegions has been
- * accepted by desktopGestureExclusionListener, it will be updated in the appropriate classes.
+ * Updates active desktop gesture exclusion regions.
+ *
+ * If [desktopExclusionRegions] is accepted by [desktopGestureExclusionListener], updates it in
+ * appropriate classes.
*/
fun updateTaskExclusionRegions(taskId: Int, taskExclusionRegions: Region) {
desktopExclusionRegions.put(taskId, taskExclusionRegions)
@@ -391,9 +293,10 @@
}
/**
- * Removes the desktop gesture exclusion region for the specified task; if exclusionRegion has
- * been accepted by desktopGestureExclusionListener, it will be updated in the appropriate
- * classes.
+ * Removes desktop gesture exclusion region for the specified task.
+ *
+ * If [desktopExclusionRegions] is accepted by [desktopGestureExclusionListener], updates it in
+ * appropriate classes.
*/
fun removeExclusionRegion(taskId: Int) {
desktopExclusionRegions.delete(taskId)
@@ -403,26 +306,24 @@
}
/** Removes and returns the bounds saved before maximizing the given task. */
- fun removeBoundsBeforeMaximize(taskId: Int): Rect? {
- return boundsBeforeMaximizeByTaskId.removeReturnOld(taskId)
- }
+ fun removeBoundsBeforeMaximize(taskId: Int): Rect? =
+ boundsBeforeMaximizeByTaskId.removeReturnOld(taskId)
/** Saves the bounds of the given task before maximizing. */
- fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) {
+ fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) =
boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
- }
internal fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopModeTaskRepository")
- dumpDisplayData(pw, innerPrefix)
+ dumpDesktopTaskData(pw, innerPrefix)
pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
}
- private fun dumpDisplayData(pw: PrintWriter, prefix: String) {
+ private fun dumpDesktopTaskData(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
- displayData.forEach { displayId, data ->
+ desktopTaskDataByDisplayId.forEach { displayId, data ->
pw.println("${prefix}Display $displayId:")
pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
@@ -432,23 +333,28 @@
}
}
- /**
- * Defines interface for classes that can listen to changes for active tasks in desktop mode.
- */
+ /** Listens to changes for active tasks in desktop mode. */
interface ActiveTasksListener {
- /** Called when the active tasks change in desktop mode. */
fun onActiveTasksChanged(displayId: Int) {}
}
- /**
- * Defines interface for classes that can listen to changes for visible tasks in desktop mode.
- */
+ /** Listens to changes for visible tasks in desktop mode. */
interface VisibleTasksListener {
- /** Called when the desktop changes the number of visible freeform tasks. */
fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {}
}
+
+ private fun logD(msg: String, vararg arguments: Any?) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ private fun logW(msg: String, vararg arguments: Any?) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
+ companion object {
+ private const val TAG = "DesktopModeTaskRepository"
+ }
}
-private fun <T> Iterable<T>.toDumpString(): String {
- return joinToString(separator = ", ", prefix = "[", postfix = "]")
-}
+private fun <T> Iterable<T>.toDumpString(): String =
+ joinToString(separator = ", ", prefix = "[", postfix = "]")
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index da212e7..9fcf73d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -52,8 +52,10 @@
val idealSize = calculateIdealSize(screenBounds, scale)
// If no top activity exists, apps fullscreen bounds and aspect ratio cannot be calculated.
// Instead default to the desired initial bounds.
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
val topActivityInfo =
- taskInfo.topActivityInfo ?: return positionInScreen(idealSize, screenBounds)
+ taskInfo.topActivityInfo ?: return positionInScreen(idealSize, stableBounds)
val initialSize: Size =
when (taskInfo.configuration.orientation) {
@@ -100,7 +102,7 @@
}
}
- return positionInScreen(initialSize, screenBounds)
+ return positionInScreen(initialSize, stableBounds)
}
/**
@@ -163,17 +165,11 @@
}
/** Adjusts bounds to be positioned in the middle of the screen. */
-private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect {
- // TODO(b/325240051): Position apps with bottom heavy offset
- val heightOffset = (screenBounds.height() - desiredSize.height) / 2
- val widthOffset = (screenBounds.width() - desiredSize.width) / 2
- return Rect(
- widthOffset,
- heightOffset,
- desiredSize.width + widthOffset,
- desiredSize.height + heightOffset
- )
-}
+private fun positionInScreen(desiredSize: Size, stableBounds: Rect): Rect =
+ Rect(0, 0, desiredSize.width, desiredSize.height).apply {
+ val offset = DesktopTaskPosition.Center.getTopLeftCoordinates(stableBounds, this)
+ offsetTo(offset.x, offset.y)
+ }
/**
* Adjusts bounds to be positioned in the middle of the area provided, not necessarily the
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.kt
new file mode 100644
index 0000000..4ee499e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTaskPosition.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.wm.shell.desktopmode
+
+import android.app.TaskInfo
+import android.graphics.Point
+import android.graphics.Rect
+import android.view.Gravity
+import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomLeft
+import com.android.wm.shell.desktopmode.DesktopTaskPosition.BottomRight
+import com.android.wm.shell.desktopmode.DesktopTaskPosition.Center
+import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopLeft
+import com.android.wm.shell.desktopmode.DesktopTaskPosition.TopRight
+
+/**
+ * The position of a task window in desktop mode.
+ */
+sealed class DesktopTaskPosition {
+ data object Center : DesktopTaskPosition() {
+ private const val WINDOW_HEIGHT_PROPORTION = 0.375
+
+ override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point {
+ val x = (frame.width() - window.width()) / 2
+ // Position with more margin at the bottom.
+ val y = (frame.height() - window.height()) * WINDOW_HEIGHT_PROPORTION + frame.top
+ return Point(x, y.toInt())
+ }
+
+ override fun next(): DesktopTaskPosition {
+ return BottomRight
+ }
+ }
+
+ data object BottomRight : DesktopTaskPosition() {
+ override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point {
+ return Point(frame.right - window.width(), frame.bottom - window.height())
+ }
+
+ override fun next(): DesktopTaskPosition {
+ return TopLeft
+ }
+ }
+
+ data object TopLeft : DesktopTaskPosition() {
+ override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point {
+ return Point(frame.left, frame.top)
+ }
+
+ override fun next(): DesktopTaskPosition {
+ return BottomLeft
+ }
+ }
+
+ data object BottomLeft : DesktopTaskPosition() {
+ override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point {
+ return Point(frame.left, frame.bottom - window.height())
+ }
+
+ override fun next(): DesktopTaskPosition {
+ return TopRight
+ }
+ }
+
+ data object TopRight : DesktopTaskPosition() {
+ override fun getTopLeftCoordinates(frame: Rect, window: Rect): Point {
+ return Point(frame.right - window.width(), frame.top)
+ }
+
+ override fun next(): DesktopTaskPosition {
+ return Center
+ }
+ }
+
+ /**
+ * Returns the top left coordinates for the window to be placed in the given
+ * DesktopTaskPosition in the frame.
+ */
+ abstract fun getTopLeftCoordinates(frame: Rect, window: Rect): Point
+
+ abstract fun next(): DesktopTaskPosition
+}
+
+/**
+ * If the app has specified horizontal or vertical gravity layout, don't change the
+ * task position for cascading effect.
+ */
+fun canChangeTaskPosition(taskInfo: TaskInfo): Boolean {
+ taskInfo.topActivityInfo?.windowLayout?.let {
+ val horizontalGravityApplied = it.gravity.and(Gravity.HORIZONTAL_GRAVITY_MASK)
+ val verticalGravityApplied = it.gravity.and(Gravity.VERTICAL_GRAVITY_MASK)
+ return horizontalGravityApplied == 0 && verticalGravityApplied == 0
+ }
+ return true
+}
+
+/**
+ * Returns the current DesktopTaskPosition for a given window in the frame.
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+fun Rect.getDesktopTaskPosition(bounds: Rect): DesktopTaskPosition {
+ return when {
+ top == bounds.top && left == bounds.left -> TopLeft
+ top == bounds.top && right == bounds.right -> TopRight
+ bottom == bounds.bottom && left == bounds.left -> BottomLeft
+ bottom == bounds.bottom && right == bounds.right -> BottomRight
+ else -> Center
+ }
+}
+
+internal fun cascadeWindow(frame: Rect, prev: Rect, dest: Rect) {
+ val lastPos = frame.getDesktopTaskPosition(prev)
+ val candidatePos = lastPos.next()
+ val destCoord = candidatePos.getTopLeftCoordinates(frame, dest)
+ dest.offsetTo(destCoord.x, destCoord.y)
+}
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 2ef045d..2f32bc3 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
@@ -255,7 +255,7 @@
/** Gets number of visible tasks in [displayId]. */
fun visibleTaskCount(displayId: Int): Int =
- desktopModeTaskRepository.visibleTaskCount(displayId)
+ desktopModeTaskRepository.getVisibleTaskCount(displayId)
/** Returns true if any tasks are visible in Desktop Mode. */
fun isDesktopModeShowing(displayId: Int): Boolean = visibleTaskCount(displayId) > 0
@@ -403,7 +403,7 @@
* The second part of the animated drag to desktop transition, called after
* [startDragToDesktop].
*/
- private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo, freeformBounds: Rect) {
+ private fun finalizeDragToDesktop(taskInfo: RunningTaskInfo) {
ProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: finalizeDragToDesktop taskId=%d",
@@ -415,7 +415,6 @@
val taskToMinimize =
bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
addMoveToDesktopChanges(wct, taskInfo)
- wct.setBounds(taskInfo.token, freeformBounds)
val transition = dragToDesktopTransitionHandler.finishDragToDesktopTransition(wct)
transition?.let { addPendingMinimizeTransition(it, taskToMinimize) }
}
@@ -446,14 +445,7 @@
if (desktopModeTaskRepository.isOnlyVisibleNonClosingTask(taskId)) {
removeWallpaperActivity(wct)
}
- if (!desktopModeTaskRepository.addClosingTask(displayId, taskId)) {
- // Could happen if the task hasn't been removed from closing list after it disappeared
- ProtoLog.w(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: the task with taskId=%d is already closing!",
- taskId
- )
- }
+ desktopModeTaskRepository.addClosingTask(displayId, taskId)
}
/** Move a task with given `taskId` to fullscreen */
@@ -794,7 +786,7 @@
}
val nonMinimizedTasksOrderedFrontToBack =
- desktopModeTaskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId)
+ desktopModeTaskRepository.getActiveNonMinimizedOrderedTasks(displayId)
// If we're adding a new Task we might need to minimize an old one
val taskToMinimize: RunningTaskInfo? =
if (newTaskIdInFront != null && desktopTasksLimiter.isPresent) {
@@ -812,7 +804,7 @@
.filter { taskId -> taskId != taskToMinimize?.taskId }
.mapNotNull { taskId -> shellTaskOrganizer.getRunningTaskInfo(taskId) }
.reversed() // Start from the back so the front task is brought forward last
- .forEach { task -> wct.reorder(task.token, true /* onTop */) }
+ .forEach { task -> wct.reorder(task.token, /* onTop= */ true) }
return taskToMinimize
}
@@ -820,7 +812,7 @@
shellTaskOrganizer
.getRunningTasks(context.displayId)
.firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
- ?.let { homeTask -> wct.reorder(homeTask.getToken(), toTop /* onTop */) }
+ ?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ toTop) }
}
private fun addWallpaperActivity(wct: WindowContainerTransaction) {
@@ -1069,14 +1061,7 @@
// Remove wallpaper activity when the last active task is removed
removeWallpaperActivity(wct)
}
- if (!desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)) {
- // Could happen if the task hasn't been removed from closing list after it disappeared
- ProtoLog.w(
- WM_SHELL_DESKTOP_MODE,
- "DesktopTasksController: the task with taskId=%d is already closing!",
- task.taskId
- )
- }
+ desktopModeTaskRepository.addClosingTask(task.displayId, task.taskId)
// If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
if (Flags.enableDesktopWindowingBackNavigation() &&
desktopModeTaskRepository.isVisibleTask(task.taskId)) {
@@ -1085,10 +1070,12 @@
return if (wct.isEmpty) null else wct
}
- private fun addMoveToDesktopChanges(
+ @VisibleForTesting
+ fun addMoveToDesktopChanges(
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
val targetWindowingMode =
@@ -1098,6 +1085,28 @@
} else {
WINDOWING_MODE_FREEFORM
}
+ val initialBounds = if (DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) {
+ calculateInitialBounds(displayLayout, taskInfo)
+ } else {
+ getDefaultDesktopTaskBounds(displayLayout)
+ }
+
+ if (DesktopModeFlags.CASCADING_WINDOWS.isEnabled(context)) {
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ val activeTasks = desktopModeTaskRepository
+ .getActiveNonMinimizedOrderedTasks(taskInfo.displayId)
+ activeTasks.firstOrNull()?.let { activeTask ->
+ shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let {
+ cascadeWindow(stableBounds,
+ it.configuration.windowConfiguration.bounds, initialBounds)
+ }
+ }
+ }
+ if (canChangeTaskPosition(taskInfo)) {
+ wct.setBounds(taskInfo.token, initialBounds)
+ }
wct.setWindowingMode(taskInfo.token, targetWindowingMode)
wct.reorder(taskInfo.token, true /* onTop */)
if (useDesktopOverrideDensity()) {
@@ -1357,16 +1366,10 @@
val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
when (indicatorType) {
IndicatorType.TO_DESKTOP_INDICATOR -> {
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId)
- ?: return IndicatorType.NO_INDICATOR
// Start a new jank interaction for the drag release to desktop window animation.
interactionJankMonitor.begin(taskSurface, context,
CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop")
- if (DesktopModeFlags.DYNAMIC_INITIAL_BOUNDS.isEnabled(context)) {
- finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo))
- } else {
- finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
- }
+ finalizeDragToDesktop(taskInfo)
}
IndicatorType.NO_INDICATOR,
IndicatorType.TO_FULLSCREEN_INDICATOR -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index c5ed1be..a011ff5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -65,10 +65,10 @@
}
override fun onTransitionReady(
- transition: IBinder,
- info: TransitionInfo,
- startTransaction: SurfaceControl.Transaction,
- finishTransaction: SurfaceControl.Transaction
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction
) {
val taskToMinimize = mPendingTransitionTokensAndTasks.remove(transition) ?: return
@@ -129,8 +129,7 @@
}
fun removeLeftoverMinimizedTasks(displayId: Int, wct: WindowContainerTransaction) {
- if (taskRepository
- .getActiveNonMinimizedTasksOrderedFrontToBack(displayId).isNotEmpty()) {
+ if (taskRepository.getActiveNonMinimizedOrderedTasks(displayId).isNotEmpty()) {
return
}
val remainingMinimizedTasks = taskRepository.getMinimizedTasks(displayId)
@@ -178,7 +177,7 @@
"DesktopTasksLimiter: addMinimizeBackTaskChangesIfNeeded, newFrontTask=%d",
newFrontTaskInfo.taskId)
val newTaskListOrderedFrontToBack = createOrderedTaskListWithGivenTaskInFront(
- taskRepository.getActiveNonMinimizedTasksOrderedFrontToBack(displayId),
+ taskRepository.getActiveNonMinimizedOrderedTasks(displayId),
newFrontTaskInfo.taskId)
val taskToMinimize = getTaskToMinimizeIfNeeded(newTaskListOrderedFrontToBack)
if (taskToMinimize != null) {
@@ -242,7 +241,5 @@
}
@VisibleForTesting
- fun getTransitionObserver(): TransitionObserver {
- return minimizeTransitionObserver
- }
+ fun getTransitionObserver(): TransitionObserver = minimizeTransitionObserver
}
\ No newline at end of file
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 e4aa115..d03a561 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
@@ -21,11 +21,11 @@
import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
@@ -40,6 +40,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
@@ -290,7 +291,7 @@
final int activityType = taskInfo1.getActivityType();
if (activityType == ACTIVITY_TYPE_STANDARD) {
Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
- int bgColor1 = getResizingBackgroundColor(taskInfo1).toArgb();
+ int bgColor1 = getResizingBackgroundColor(taskInfo1);
mDropZoneView1.setAppInfo(bgColor1, icon1);
mDropZoneView2.setAppInfo(bgColor1, icon1);
mDropZoneView1.setForceIgnoreBottomMargin(false);
@@ -312,10 +313,10 @@
mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
if (topOrLeftTask != null && bottomOrRightTask != null) {
Drawable topOrLeftIcon = mIconProvider.getIcon(topOrLeftTask.topActivityInfo);
- int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask).toArgb();
+ int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask);
Drawable bottomOrRightIcon = mIconProvider.getIcon(
bottomOrRightTask.topActivityInfo);
- int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask).toArgb();
+ int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask);
mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon);
mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon);
}
@@ -586,6 +587,11 @@
}
}
+ private static int getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
+ return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
+ }
+
/**
* Dumps information about this drag layout.
*/
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 4531967..229d972 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
@@ -101,12 +101,9 @@
repository.addOrMoveFreeformTaskToTop(taskInfo.displayId, taskInfo.taskId);
repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
if (taskInfo.isVisible) {
- if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- }
+ repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId,
- true);
+ true);
}
});
}
@@ -122,10 +119,7 @@
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.removeFreeformTask(taskInfo.displayId, taskInfo.taskId);
repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId);
- if (repository.removeActiveTask(taskInfo.taskId)) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Removing active freeform task: #%d", taskInfo.taskId);
- }
+ repository.removeActiveTask(taskInfo.taskId, /* excludedDisplayId= */ null);
repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, false);
});
}
@@ -146,14 +140,9 @@
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
mDesktopModeTaskRepository.ifPresent(repository -> {
if (taskInfo.isVisible) {
- if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- }
- } else if (repository.isClosingTask(taskInfo.taskId)
- && repository.removeClosingTask(taskInfo.taskId)) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Removing closing freeform task: #%d", taskInfo.taskId);
+ repository.addActiveTask(taskInfo.displayId, taskInfo.taskId);
+ } else if (repository.isClosingTask(taskInfo.taskId)) {
+ repository.removeClosingTask(taskInfo.taskId);
}
repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId,
taskInfo.isVisible);
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 a52141c5..ba97c832 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
@@ -21,6 +21,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.util.RotationUtils.deltaRotation;
import static android.util.RotationUtils.rotateBounds;
+import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -1183,10 +1184,15 @@
? pipTaskInfo.configuration.windowConfiguration.getBounds()
: mPipOrganizer.mAppBounds;
+ // Populate the final surface control transactions from PipTransitionAnimator,
+ // display cutout insets is handled in the swipe pip to home animator, empty it out here
+ // to avoid flicker.
+ final Rect savedDisplayCutoutInsets = new Rect(pipTaskInfo.displayCutoutInsets);
+ pipTaskInfo.displayCutoutInsets.setEmpty();
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
- 0 /* startingAngle */, 0 /* rotationDelta */)
+ 0 /* startingAngle */, ROTATION_0 /* rotationDelta */)
.setPipTransactionHandler(mTransactionConsumer)
.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
// The start state is the end state for swipe-auto-pip.
@@ -1194,6 +1200,7 @@
animator.applySurfaceControlTransaction(leash, startTransaction,
PipAnimationController.FRACTION_END);
startTransaction.apply();
+ pipTaskInfo.displayCutoutInsets.set(savedDisplayCutoutInsets);
mPipBoundsState.setBounds(destinationBounds);
final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
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 df3803d..999ab95 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
@@ -416,6 +416,17 @@
// location now.
mSpringingToTouch = false;
+ // Boost the velocityX if it's zero to forcefully push it towards the nearest edge.
+ // We don't simply change the xEndValue below since the PhysicsAnimator would rely on the
+ // same velocityX to find out which edge to snap to.
+ if (velocityX == 0) {
+ final int motionCenterX = mPipBoundsState
+ .getMotionBoundsState().getBoundsInMotion().centerX();
+ final int displayCenterX = mPipBoundsState
+ .getDisplayBounds().centerX();
+ velocityX = (motionCenterX < displayCenterX) ? -0.001f : 0.001f;
+ }
+
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
.spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
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 b939b16..8aa0933 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
@@ -44,6 +44,7 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.ImeListener;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -56,7 +57,6 @@
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -201,6 +201,11 @@
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
}
});
+ mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+ new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) {
+ @Override
+ public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
+ });
// Allow other outside processes to bind to PiP controller using the key below.
mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
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 ea02de9d..e1e072a 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
@@ -416,6 +416,17 @@
// location now.
mSpringingToTouch = false;
+ // Boost the velocityX if it's zero to forcefully push it towards the nearest edge.
+ // We don't simply change the xEndValue below since the PhysicsAnimator would rely on the
+ // same velocityX to find out which edge to snap to.
+ if (velocityX == 0) {
+ final int motionCenterX = mPipBoundsState
+ .getMotionBoundsState().getBoundsInMotion().centerX();
+ final int displayCenterX = mPipBoundsState
+ .getDisplayBounds().centerX();
+ velocityX = (motionCenterX < displayCenterX) ? -0.001f : 0.001f;
+ }
+
mTemporaryBoundsPhysicsAnimator
.spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
.spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
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 d7ee563..2531ff1 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
@@ -41,7 +41,6 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
-import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
@@ -2458,13 +2457,8 @@
updateSurfaceBounds(layout, t, shouldUseParallaxEffect);
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
- // TODO (b/307490004): "commonColor" below is a temporary fix to ensure the colors on both
- // sides match. When b/307490004 is fixed, this code can be reverted.
- float[] commonColor = getResizingBackgroundColor(mSideStage.mRootTaskInfo).getComponents();
- mMainStage.onResizing(
- mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately, commonColor);
- mSideStage.onResizing(
- mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately, commonColor);
+ mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
+ mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
t.apply();
mTransactionPool.release(t);
}
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 1076eca..d1ab3e9 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
@@ -314,10 +314,10 @@
}
void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
- int offsetY, boolean immediately, float[] veilColor) {
+ int offsetY, boolean immediately) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
- offsetY, immediately, veilColor);
+ offsetY, immediately);
}
}
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 7784784..d8c8c60 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
@@ -502,7 +502,8 @@
backgroundColorForTransition = getTransitionBackgroundColorIfSet(info, change, a,
backgroundColorForTransition);
- if (!isTask && a.hasExtension()) {
+ if (!com.android.graphics.libgui.flags.Flags.edgeExtensionShader() && !isTask
+ && a.getExtensionEdges() != 0) {
if (!TransitionUtil.isOpeningType(mode)) {
// Can screenshot now (before startTransaction is applied)
edgeExtendWindow(change, a, startTransaction, finishTransaction);
@@ -512,6 +513,8 @@
postStartTransactionCallbacks
.add(t -> edgeExtendWindow(change, a, t, finishTransaction));
}
+ } else if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+ finishTransaction.setEdgeExtensionEffect(change.getLeash(), /* edge */ 0);
}
final Rect clipRect = TransitionUtil.isClosingType(mode)
@@ -1008,6 +1011,10 @@
Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
tmpTransformation.clear();
anim.getTransformation(time, tmpTransformation);
+ if (anim.getExtensionEdges() != 0
+ && com.android.graphics.libgui.flags.Flags.edgeExtensionShader()) {
+ t.setEdgeExtensionEffect(leash, anim.getExtensionEdges());
+ }
if (position != null) {
tmpTransformation.getMatrix().postTranslate(position.x, position.y);
}
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 7f1f365..0ff3522 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
@@ -30,6 +30,7 @@
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
import static android.view.MotionEvent.ACTION_HOVER_EXIT;
import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_OUTSIDE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
@@ -502,8 +503,6 @@
if (!decoration.isHandleMenuActive()) {
moveTaskToFront(decoration.mTaskInfo);
decoration.createHandleMenu(mSplitScreenController);
- } else {
- decoration.closeHandleMenu();
}
} else if (id == R.id.desktop_button) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -549,6 +548,17 @@
@Override
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ if (e.getActionMasked() == ACTION_OUTSIDE) {
+ if (id == R.id.handle_menu) {
+ // Close handle menu on outside touch if menu is directly touchable; if not,
+ // it will be handled by handleEventOutsideCaption.
+ if (decoration.mTaskInfo.isFreeform()
+ || Flags.enableAdditionalWindowsAboveStatusBar()) {
+ decoration.closeHandleMenu();
+ }
+ }
+ }
if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) {
mTouchscreenInUse = e.getActionMasked() != ACTION_UP
&& e.getActionMasked() != ACTION_CANCEL;
@@ -558,7 +568,6 @@
&& id != R.id.maximize_window) {
return false;
}
- final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
moveTaskToFront(decoration.mTaskInfo);
final int actionMasked = e.getActionMasked();
@@ -587,7 +596,6 @@
mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion
&& downInExclusionRegion && isTransparentCaption) && !isResizeEvent;
}
-
if (!mShouldPilferCaptionEvents) {
// The event will be handled by a window below or pilfered by resize handler.
return false;
@@ -601,9 +609,6 @@
// Gesture is finished, reset state.
mShouldPilferCaptionEvents = false;
}
- if (!mHasLongClicked && id != R.id.maximize_window) {
- decoration.closeMaximizeMenuIfNeeded(e);
- }
return mDragDetector.onMotionEvent(v, e);
}
@@ -896,10 +901,6 @@
*
* @param relevantDecor the window decoration of the focused task's caption. This method only
* handles motion events outside this caption's bounds.
- * TODO(b/349135068): Outside-touch detection no longer works with the
- * enableAdditionalWindowsAboveStatusBar flag enabled. This
- * will be fixed once we can add FLAG_WATCH_OUTSIDE_TOUCH to relevant menus,
- * at which point, all EventReceivers and external touch logic should be removed.
*/
private void handleEventOutsideCaption(MotionEvent ev,
DesktopModeWindowDecoration relevantDecor) {
@@ -910,9 +911,8 @@
relevantDecor.updateHoverAndPressStatus(ev);
final int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- if (!mTransitionDragActive) {
+ if (!mTransitionDragActive && !Flags.enableAdditionalWindowsAboveStatusBar()) {
relevantDecor.closeHandleMenuIfNeeded(ev);
- relevantDecor.closeMaximizeMenuIfNeeded(ev);
}
}
}
@@ -1250,7 +1250,6 @@
}
}
-
private class DragStartListenerImpl
implements DragPositioningCallbackUtility.DragStartListener {
@Override
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 9a7ce67..41f428b 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
@@ -19,6 +19,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.windowingModeToString;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
@@ -593,6 +595,17 @@
// through to the windows below so that the app can respond to input events on
// their custom content.
relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
+ } else {
+ if (Flags.enableCaptionCompatInsetForceConsumption()) {
+ // Force-consume the caption bar insets when the app tries to hide the caption.
+ // This improves app compatibility of immersive apps.
+ relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING;
+ }
+ }
+ if (Flags.enableCaptionCompatInsetForceConsumptionAlways()) {
+ // Always force-consume the caption bar insets for maximum app compatibility,
+ // including non-immersive apps that just don't handle caption insets properly.
+ relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
}
// Report occluding elements as bounding rects to the insets system so that apps can
// draw in the empty space in the center:
@@ -725,7 +738,11 @@
return;
}
final PackageManager pm = mContext.getApplicationContext().getPackageManager();
- final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
+ final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity,
+ // Include uninstalled apps. Despite its name, adding this flag is a workaround
+ // to #getActivityInfo throwing a NameNotFoundException for installed packages
+ // when HSUM is enabled. See b/354884302.
+ PackageManager.MATCH_UNINSTALLED_PACKAGES);
final IconProvider provider = new IconProvider(mContext);
final Drawable appIconDrawable = provider.getIcon(activityInfo);
final BaseIconFactory headerIconFactory = createIconFactory(mContext,
@@ -891,6 +908,10 @@
mIsMaximizeMenuHovered = hovered;
onMaximizeHoverStateChanged();
return null;
+ },
+ () -> {
+ closeMaximizeMenu();
+ return null;
}
);
}
@@ -1101,8 +1122,13 @@
handle.performClick();
}
if (isHandleMenuActive()) {
- mHandleMenu.checkMotionEvent(ev);
- closeHandleMenuIfNeeded(ev);
+ // If the whole handle menu can be touched directly, rely on FLAG_WATCH_OUTSIDE_TOUCH.
+ // This is for the case that some of the handle menu is underneath the status bar.
+ if (isAppHandle(mWindowDecorViewHolder)
+ && !Flags.enableAdditionalWindowsAboveStatusBar()) {
+ mHandleMenu.checkMotionEvent(ev);
+ closeHandleMenuIfNeeded(ev);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 32522c6..7a81d4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -31,6 +31,7 @@
import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.View
+import android.view.WindowManager
import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView
@@ -140,7 +141,9 @@
x = x,
y = y,
width = menuWidth,
- height = menuHeight
+ height = menuHeight,
+ flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
)
} else {
parentDecor.addWindow(
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 5f9f8d6..aa2ce0f 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
@@ -40,6 +40,7 @@
import android.view.MotionEvent.ACTION_HOVER_ENTER
import android.view.MotionEvent.ACTION_HOVER_EXIT
import android.view.MotionEvent.ACTION_HOVER_MOVE
+import android.view.MotionEvent.ACTION_OUTSIDE
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.SurfaceControlViewHost
@@ -104,14 +105,16 @@
onMaximizeClickListener: OnTaskActionClickListener,
onLeftSnapClickListener: OnTaskActionClickListener,
onRightSnapClickListener: OnTaskActionClickListener,
- onHoverListener: (Boolean) -> Unit
+ onHoverListener: (Boolean) -> Unit,
+ onOutsideTouchListener: () -> Unit,
) {
if (maximizeMenu != null) return
createMaximizeMenu(
onMaximizeClickListener = onMaximizeClickListener,
onLeftSnapClickListener = onLeftSnapClickListener,
onRightSnapClickListener = onRightSnapClickListener,
- onHoverListener = onHoverListener
+ onHoverListener = onHoverListener,
+ onOutsideTouchListener = onOutsideTouchListener
)
maximizeMenuView?.animateOpenMenu()
}
@@ -129,7 +132,8 @@
onMaximizeClickListener: OnTaskActionClickListener,
onLeftSnapClickListener: OnTaskActionClickListener,
onRightSnapClickListener: OnTaskActionClickListener,
- onHoverListener: (Boolean) -> Unit
+ onHoverListener: (Boolean) -> Unit,
+ onOutsideTouchListener: () -> Unit
) {
val t = transactionSupplier.get()
val builder = SurfaceControl.Builder()
@@ -142,7 +146,8 @@
menuWidth,
menuHeight,
WindowManager.LayoutParams.TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSPARENT
)
lp.title = "Maximize Menu for Task=" + taskInfo.taskId
@@ -172,6 +177,7 @@
onRightSnapClickListener.onClick(taskId, "right_snap_option")
}
menuView.onMenuHoverListener = onHoverListener
+ menuView.onOutsideTouchListener = onOutsideTouchListener
viewHost.setView(menuView.rootView, lp)
}
@@ -268,6 +274,8 @@
var onRightSnapClickListener: (() -> Unit)? = null
/** Invoked whenever the hover state of the menu changes. */
var onMenuHoverListener: ((Boolean) -> Unit)? = null
+ /** Invoked whenever a click occurs outside the menu */
+ var onOutsideTouchListener: (() -> Unit)? = null
init {
overlay.setOnHoverListener { _, event ->
@@ -312,6 +320,13 @@
maximizeButton.setOnClickListener { onMaximizeClickListener?.invoke() }
snapRightButton.setOnClickListener { onRightSnapClickListener?.invoke() }
snapLeftButton.setOnClickListener { onLeftSnapClickListener?.invoke() }
+ rootView.setOnTouchListener { _, event ->
+ if (event.actionMasked == ACTION_OUTSIDE) {
+ onOutsideTouchListener?.invoke()
+ false
+ }
+ true
+ }
// To prevent aliasing.
maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index a691f59..3b8657d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -19,10 +19,10 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
-import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import android.annotation.NonNull;
@@ -54,7 +54,6 @@
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -375,7 +374,8 @@
}
final WindowDecorationInsets newInsets = new WindowDecorationInsets(
- mTaskInfo.token, mOwner, captionInsetsRect, boundingRects);
+ mTaskInfo.token, mOwner, captionInsetsRect, boundingRects,
+ params.mInsetSourceFlags);
if (!newInsets.equals(mWindowDecorationInsets)) {
// Add or update this caption as an insets source.
mWindowDecorationInsets = newInsets;
@@ -635,7 +635,8 @@
.show(windowSurfaceControl);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(width, height, TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSPARENT);
lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
@@ -660,7 +661,7 @@
final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
final WindowDecorationInsets newInsets = new WindowDecorationInsets(mTaskInfo.token,
- mOwner, captionInsets, null /* boundingRets */);
+ mOwner, captionInsets, null /* boundingRets */, 0 /* flags */);
if (!newInsets.equals(mWindowDecorationInsets)) {
mWindowDecorationInsets = newInsets;
mWindowDecorationInsets.addOrUpdate(wct);
@@ -674,6 +675,7 @@
int mCaptionWidthId;
final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
int mInputFeatures;
+ @InsetsSource.Flags int mInsetSourceFlags;
int mShadowRadiusId;
int mCornerRadius;
@@ -689,6 +691,7 @@
mCaptionWidthId = Resources.ID_NULL;
mOccludingCaptionElements.clear();
mInputFeatures = 0;
+ mInsetSourceFlags = 0;
mShadowRadiusId = Resources.ID_NULL;
mCornerRadius = 0;
@@ -753,20 +756,20 @@
private final Binder mOwner;
private final Rect mFrame;
private final Rect[] mBoundingRects;
+ private final @InsetsSource.Flags int mFlags;
private WindowDecorationInsets(WindowContainerToken token, Binder owner, Rect frame,
- Rect[] boundingRects) {
+ Rect[] boundingRects, @InsetsSource.Flags int flags) {
mToken = token;
mOwner = owner;
mFrame = frame;
mBoundingRects = boundingRects;
+ mFlags = flags;
}
void addOrUpdate(WindowContainerTransaction wct) {
- final @InsetsSource.Flags int captionSourceFlags =
- Flags.enableCaptionCompatInsetForceConsumption() ? FLAG_FORCE_CONSUMING : 0;
wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects,
- captionSourceFlags);
+ mFlags);
wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame,
mBoundingRects, 0 /* flags */);
}
@@ -782,12 +785,13 @@
if (!(o instanceof WindowDecoration.WindowDecorationInsets that)) return false;
return Objects.equals(mToken, that.mToken) && Objects.equals(mOwner,
that.mOwner) && Objects.equals(mFrame, that.mFrame)
- && Objects.deepEquals(mBoundingRects, that.mBoundingRects);
+ && Objects.deepEquals(mBoundingRects, that.mBoundingRects)
+ && mFlags == that.mFlags;
}
@Override
public int hashCode() {
- return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects));
+ return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects), mFlags);
}
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index 6a354f1..f1370bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -35,6 +35,7 @@
y: Int,
width: Int,
height: Int,
+ flags: Int,
layoutId: Int? = null
) : AdditionalViewContainer() {
override val view: View
@@ -49,7 +50,7 @@
val lp = WindowManager.LayoutParams(
width, height, x, y,
WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ flags,
PixelFormat.TRANSPARENT
).apply {
title = "Additional view container of Task=$taskId"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 57d8cac..753723c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -97,7 +97,8 @@
handleHeight: Int) {
if (!Flags.enableAdditionalWindowsAboveStatusBar()) return
statusBarInputLayer = AdditionalSystemViewContainer(context, taskInfo.taskId,
- handlePosition.x, handlePosition.y, handleWidth, handleHeight)
+ handlePosition.x, handlePosition.y, handleWidth, handleHeight,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
val view = statusBarInputLayer?.view
val lp = view?.layoutParams as WindowManager.LayoutParams
lp.title = "Handle Input Layer of task " + taskInfo.taskId
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 2c0aa12..764d5a9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -83,7 +83,7 @@
}
}, mExecutor) {
@Override
- void removeImeSurface() { }
+ void removeImeSurface(int displayId) { }
}.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/ImeListenerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/ImeListenerTest.kt
new file mode 100644
index 0000000..3b0a072
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/ImeListenerTest.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.common
+
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Insets
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.view.DisplayCutout
+import android.view.DisplayInfo
+import android.view.InsetsSource.ID_IME
+import android.view.InsetsState
+import android.view.Surface
+import android.view.WindowInsets.Type
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.wm.shell.ShellTestCase
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.kotlin.whenever
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ImeListenerTest : ShellTestCase() {
+ private lateinit var imeListener: CachingImeListener
+ private lateinit var displayLayout: DisplayLayout
+
+ @Mock private lateinit var displayController: DisplayController
+ @Before
+ fun setUp() {
+ val resources = createResources(40, 50, false)
+ val displayInfo = createDisplayInfo(1000, 1500, 0, Surface.ROTATION_0)
+ displayLayout = DisplayLayout(displayInfo, resources, false, false)
+ whenever(displayController.getDisplayLayout(DEFAULT_DISPLAY_ID)).thenReturn(displayLayout)
+ imeListener = CachingImeListener(displayController, DEFAULT_DISPLAY_ID)
+ }
+
+ @Test
+ fun testImeAppears() {
+ val insetsState = createInsetsStateWithIme(true, DEFAULT_IME_HEIGHT)
+ imeListener.insetsChanged(insetsState)
+ assertTrue("Ime insets source should become visible", imeListener.cachedImeVisible)
+ assertEquals(DEFAULT_IME_HEIGHT, imeListener.cachedImeHeight)
+ }
+
+ @Test
+ fun testImeAppears_thenDisappears() {
+ // Send insetsState with an IME as a visible source.
+ val insetsStateWithIme = createInsetsStateWithIme(true, DEFAULT_IME_HEIGHT)
+ imeListener.insetsChanged(insetsStateWithIme)
+
+ // Send insetsState without IME.
+ val insetsStateWithoutIme = createInsetsStateWithIme(false, 0)
+ imeListener.insetsChanged(insetsStateWithoutIme)
+
+ assertFalse("Ime insets source should become invisible",
+ imeListener.cachedImeVisible)
+ assertEquals(0, imeListener.cachedImeHeight)
+ }
+
+ private fun createInsetsStateWithIme(isVisible: Boolean, imeHeight: Int): InsetsState {
+ val stableBounds = Rect()
+ displayLayout.getStableBounds(stableBounds)
+ val insetsState = InsetsState()
+
+ val insetsSource = insetsState.getOrCreateSource(ID_IME, Type.ime())
+ insetsSource.setVisible(isVisible)
+ insetsSource.setFrame(stableBounds.left, stableBounds.bottom - imeHeight,
+ stableBounds.right, stableBounds.bottom)
+ return insetsState
+ }
+
+ private fun createDisplayInfo(width: Int, height: Int, cutoutHeight: Int,
+ rotation: Int): DisplayInfo {
+ val info = DisplayInfo()
+ info.logicalWidth = width
+ info.logicalHeight = height
+ info.rotation = rotation
+ if (cutoutHeight > 0) {
+ info.displayCutout = DisplayCutout(
+ Insets.of(0, cutoutHeight, 0, 0) /* safeInsets */,
+ null /* boundLeft */,
+ Rect(width / 2 - cutoutHeight, 0, width / 2 + cutoutHeight,
+ cutoutHeight) /* boundTop */, null /* boundRight */,
+ null /* boundBottom */)
+ } else {
+ info.displayCutout = DisplayCutout.NO_CUTOUT
+ }
+ info.logicalDensityDpi = 300
+ return info
+ }
+
+ private fun createResources(navLand: Int, navPort: Int, navMoves: Boolean): Resources {
+ val cfg = Configuration()
+ cfg.uiMode = Configuration.UI_MODE_TYPE_NORMAL
+ val res = Mockito.mock(Resources::class.java)
+ Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_height_landscape_car_mode)
+ Mockito.doReturn(navPort).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_height_car_mode)
+ Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_width_car_mode)
+ Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_height_landscape)
+ Mockito.doReturn(navPort).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_height)
+ Mockito.doReturn(navLand).whenever(res).getDimensionPixelSize(
+ R.dimen.navigation_bar_width)
+ Mockito.doReturn(navMoves).whenever(res).getBoolean(R.bool.config_navBarCanMove)
+ Mockito.doReturn(cfg).whenever(res).configuration
+ return res
+ }
+
+ private class CachingImeListener(
+ displayController: DisplayController,
+ displayId: Int
+ ) : ImeListener(displayController, displayId) {
+ var cachedImeVisible = false
+ var cachedImeHeight = 0
+ public override fun onImeVisibilityChanged(imeVisible: Boolean, imeHeight: Int) {
+ cachedImeVisible = imeVisible
+ cachedImeHeight = imeHeight
+ }
+ }
+
+ companion object {
+ private const val DEFAULT_DISPLAY_ID = 0
+ private const val DEFAULT_IME_HEIGHT = 500
+ }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index 18b08bf..0a5672d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -41,32 +41,44 @@
}
@Test
- fun addActiveTask_listenerNotifiedAndTaskIsActive() {
+ fun addActiveTask_notifiesListener() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1)
+ }
+
+ @Test
+ fun addActiveTask_taskIsActive() {
+ val listener = TestListener()
+ repo.addActiveTaskListener(listener)
+
+ repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
assertThat(repo.isActiveTask(1)).isTrue()
}
@Test
- fun addActiveTask_sameTaskDoesNotNotify() {
+ fun addSameActiveTaskTwice_notifiesOnce() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(1)
}
@Test
- fun addActiveTask_multipleTasksAddedNotifiesForEach() {
+ fun addActiveTask_multipleTasksAdded_notifiesForAllTasks() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 2)
+
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
}
@@ -84,22 +96,35 @@
}
@Test
- fun removeActiveTask_listenerNotifiedAndTaskNotActive() {
+ fun removeActiveTask_notifiesListener() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
-
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
repo.removeActiveTask(1)
+
// Notify once for add and once for remove
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(2)
+ }
+
+ @Test
+ fun removeActiveTask_taskNotActive() {
+ val listener = TestListener()
+ repo.addActiveTaskListener(listener)
+ repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
+ repo.removeActiveTask(1)
+
assertThat(repo.isActiveTask(1)).isFalse()
}
@Test
- fun removeActiveTask_removeNotExistingTaskDoesNotNotify() {
+ fun removeActiveTask_nonExistingTask_doesNotNotify() {
val listener = TestListener()
repo.addActiveTaskListener(listener)
+
repo.removeActiveTask(99)
+
assertThat(listener.activeChangesOnDefaultDisplay).isEqualTo(0)
}
@@ -108,32 +133,38 @@
val listener = TestListener()
repo.addActiveTaskListener(listener)
repo.addActiveTask(DEFAULT_DISPLAY, taskId = 1)
+
repo.removeActiveTask(1)
+
assertThat(listener.activeChangesOnSecondaryDisplay).isEqualTo(0)
assertThat(repo.isActiveTask(1)).isFalse()
}
@Test
- fun isActiveTask_notExistingTaskReturnsFalse() {
+ fun isActiveTask_nonExistingTask_returnsFalse() {
assertThat(repo.isActiveTask(99)).isFalse()
}
@Test
- fun isOnlyVisibleNonClosingTask_noTasks() {
+ fun isOnlyVisibleNonClosingTask_noTasks_returnsFalse() {
// No visible tasks
assertThat(repo.isOnlyVisibleNonClosingTask(1)).isFalse()
+ }
+
+ @Test
+ fun isClosingTask_noTasks_returnsFalse() {
+ // No visible tasks
assertThat(repo.isClosingTask(1)).isFalse()
}
@Test
- fun isOnlyVisibleNonClosingTask_singleVisibleNonClosingTask() {
+ fun updateVisibleFreeformTasks_singleVisibleNonClosingTask_updatesTasksCorrectly() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- // The only visible task
assertThat(repo.isVisibleTask(1)).isTrue()
assertThat(repo.isClosingTask(1)).isFalse()
assertThat(repo.isOnlyVisibleNonClosingTask(1)).isTrue()
- // Not a visible task
+
assertThat(repo.isVisibleTask(99)).isFalse()
assertThat(repo.isClosingTask(99)).isFalse()
assertThat(repo.isOnlyVisibleNonClosingTask(99)).isFalse()
@@ -207,10 +238,11 @@
}
@Test
- fun addListener_notifiesVisibleFreeformTask() {
+ fun addVisibleTasksListener_notifiesVisibleFreeformTask() {
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
+
repo.addVisibleTasksListener(listener, executor)
executor.flushAll()
@@ -236,6 +268,7 @@
val listener = TestVisibilityListener()
val executor = TestShellExecutor()
repo.addVisibleTasksListener(listener, executor)
+
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
executor.flushAll()
@@ -303,6 +336,7 @@
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
executor.flushAll()
@@ -329,6 +363,7 @@
executor.flushAll()
assertThat(listener.visibleTasksCountOnDefaultDisplay).isEqualTo(2)
+
repo.updateVisibleFreeformTasks(INVALID_DISPLAY, taskId = 1, visible = false)
executor.flushAll()
@@ -337,65 +372,73 @@
}
@Test
- fun visibleTaskCount_defaultDisplay_returnsCorrectCount() {
+ fun getVisibleTaskCount_defaultDisplay_returnsCorrectCount() {
// No tasks, count is 0
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
// New task increments count to 1
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Visibility update to same task does not increase count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Second task visible increments count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(2)
// Hiding a task decrements count
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
// Hiding all tasks leaves count at 0
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 2, visible = false)
- assertThat(repo.visibleTaskCount(displayId = 9)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(displayId = 9)).isEqualTo(0)
// Hiding a not existing task, count remains at 0
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 999, visible = false)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
}
@Test
- fun visibleTaskCount_multipleDisplays_returnsCorrectCount() {
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+ fun getVisibleTaskCount_multipleDisplays_returnsCorrectCount() {
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on default display increments count for that display only
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(0)
// New task on secondary display, increments count for that display only
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 2, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(1)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
// Marking task visible on another display, updates counts for both displays
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Marking task that is on secondary display, hidden on default display, does not affect
// secondary display
repo.updateVisibleFreeformTasks(DEFAULT_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(2)
// Hiding a task on that display, decrements count
repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = false)
- assertThat(repo.visibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
- assertThat(repo.visibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
+
+ assertThat(repo.getVisibleTaskCount(DEFAULT_DISPLAY)).isEqualTo(0)
+ assertThat(repo.getVisibleTaskCount(SECOND_DISPLAY)).isEqualTo(1)
}
@Test
@@ -428,7 +471,9 @@
fun removeFreeformTask_removesTaskBoundsBeforeMaximize() {
val taskId = 1
repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200))
+
repo.removeFreeformTask(THIRD_DISPLAY, taskId)
+
assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull()
}
@@ -436,7 +481,9 @@
fun saveBoundsBeforeMaximize_boundsSavedByTaskId() {
val taskId = 1
val bounds = Rect(0, 0, 200, 200)
+
repo.saveBoundsBeforeMaximize(taskId, bounds)
+
assertThat(repo.removeBoundsBeforeMaximize(taskId)).isEqualTo(bounds)
}
@@ -446,17 +493,20 @@
val bounds = Rect(0, 0, 200, 200)
repo.saveBoundsBeforeMaximize(taskId, bounds)
repo.removeBoundsBeforeMaximize(taskId)
- assertThat(repo.removeBoundsBeforeMaximize(taskId)).isNull()
+
+ val boundsBeforeMaximize = repo.removeBoundsBeforeMaximize(taskId)
+
+ assertThat(boundsBeforeMaximize).isNull()
}
@Test
- fun minimizeTaskNotCalled_noTasksMinimized() {
+ fun isMinimizedTask_minimizeTaskNotCalled_noTasksMinimized() {
assertThat(repo.isMinimizedTask(taskId = 0)).isFalse()
assertThat(repo.isMinimizedTask(taskId = 1)).isFalse()
}
@Test
- fun minimizeTask_onlyThatTaskIsMinimized() {
+ fun minimizeTask_minimizesCorrectTask() {
repo.minimizeTask(displayId = 0, taskId = 0)
assertThat(repo.isMinimizedTask(taskId = 0)).isTrue()
@@ -465,8 +515,9 @@
}
@Test
- fun unminimizeTask_taskNoLongerMinimized() {
+ fun unminimizeTask_unminimizesTask() {
repo.minimizeTask(displayId = 0, taskId = 0)
+
repo.unminimizeTask(displayId = 0, taskId = 0)
assertThat(repo.isMinimizedTask(taskId = 0)).isFalse()
@@ -478,6 +529,7 @@
fun unminimizeTask_nonExistentTask_doesntCrash() {
repo.unminimizeTask(displayId = 0, taskId = 0)
+ // No change
assertThat(repo.isMinimizedTask(taskId = 0)).isFalse()
assertThat(repo.isMinimizedTask(taskId = 1)).isFalse()
assertThat(repo.isMinimizedTask(taskId = 2)).isFalse()
@@ -485,41 +537,44 @@
@Test
- fun updateVisibleFreeformTasks_toVisible_taskIsUnminimized() {
+ fun updateVisibleFreeformTasks_minimizedTaskBecomesVisible_unminimizesTask() {
repo.minimizeTask(displayId = 10, taskId = 2)
-
repo.updateVisibleFreeformTasks(displayId = 10, taskId = 2, visible = true)
- assertThat(repo.isMinimizedTask(taskId = 2)).isFalse()
+ val isMinimizedTask = repo.isMinimizedTask(taskId = 2)
+
+ assertThat(isMinimizedTask).isFalse()
}
@Test
- fun getActiveNonMinimizedTasksOrderedFrontToBack_returnsFreeformTasksInCorrectOrder() {
+ fun getActiveNonMinimizedOrderedTasks_returnsFreeformTasksInCorrectOrder() {
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3)
- // The front-most task will be the one added last through addOrMoveFreeformTaskToTop
+ // The front-most task will be the one added last through `addOrMoveFreeformTaskToTop`
repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3)
repo.addOrMoveFreeformTaskToTop(displayId = 0, taskId = 2)
repo.addOrMoveFreeformTaskToTop(displayId = 0, taskId = 1)
- assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack(displayId = 0))
- .containsExactly(1, 2, 3).inOrder()
+ val tasks = repo.getActiveNonMinimizedOrderedTasks(displayId = 0)
+
+ assertThat(tasks).containsExactly(1, 2, 3).inOrder()
}
@Test
- fun getActiveNonMinimizedTasksOrderedFrontToBack_minimizedTaskNotIncluded() {
+ fun getActiveNonMinimizedOrderedTasks_excludesMinimizedTasks() {
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 1)
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 2)
repo.addActiveTask(displayId = DEFAULT_DISPLAY, taskId = 3)
- // The front-most task will be the one added last through addOrMoveFreeformTaskToTop
+ // The front-most task will be the one added last through `addOrMoveFreeformTaskToTop`
repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 3)
repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 2)
repo.addOrMoveFreeformTaskToTop(displayId = DEFAULT_DISPLAY, taskId = 1)
repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- assertThat(repo.getActiveNonMinimizedTasksOrderedFrontToBack(
- displayId = DEFAULT_DISPLAY)).containsExactly(1, 3).inOrder()
+ val tasks = repo.getActiveNonMinimizedOrderedTasks(displayId = DEFAULT_DISPLAY)
+
+ assertThat(tasks).containsExactly(1, 3).inOrder()
}
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 e66018f..d5690db 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
@@ -43,6 +43,7 @@
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
+import android.view.Gravity
import android.view.SurfaceControl
import android.view.WindowManager
import android.view.WindowManager.TRANSIT_CHANGE
@@ -185,12 +186,12 @@
private val DISPLAY_DIMENSION_SHORT = 1600
private val DISPLAY_DIMENSION_LONG = 2560
- private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 200, 2240, 1400)
- private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 320, 1400, 2240)
- private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 680, 1575, 1880)
- private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 200, 1880, 1400)
- private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 699, 1575, 1861)
- private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 200, 1730, 1400)
+ private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 75, 2240, 1275)
+ private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 165, 1400, 2085)
+ private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 435, 1575, 1635)
+ private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 75, 1880, 1275)
+ private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 449, 1575, 1611)
+ private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 75, 1730, 1275)
@Before
fun setUp() {
@@ -590,6 +591,140 @@
}
@Test
+ fun addMoveToDesktopChanges_gravityLeft_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.LEFT)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isEqualTo(Rect())
+ }
+
+ @Test
+ fun addMoveToDesktopChanges_gravityRight_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.RIGHT)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isEqualTo(Rect())
+ }
+
+ @Test
+ fun addMoveToDesktopChanges_gravityTop_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.TOP)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isEqualTo(Rect())
+ }
+
+ @Test
+ fun addMoveToDesktopChanges_gravityBottom_noBoundsApplied() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask(gravity = Gravity.BOTTOM)
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(finalBounds).isEqualTo(Rect())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_positionBottomRight() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ setUpFreeformTask(bounds = DEFAULT_LANDSCAPE_BOUNDS)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.BottomRight)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_positionTopLeft() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.BottomRight, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.TopLeft)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_positionBottomLeft() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.TopLeft, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.BottomLeft)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_positionTopRight() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.BottomLeft, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.TopRight)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CASCADING_WINDOWS)
+ fun addMoveToDesktopChanges_positionResetsToCenter() {
+ setUpLandscapeDisplay()
+ val stableBounds = Rect()
+ displayLayout.getStableBoundsForDesktopMode(stableBounds)
+
+ addFreeformTaskAtPosition(DesktopTaskPosition.TopRight, stableBounds)
+
+ val task = setUpFullscreenTask()
+ val wct = WindowContainerTransaction()
+ controller.addMoveToDesktopChanges(wct, task)
+
+ val finalBounds = findBoundsChange(wct, task)
+ assertThat(stableBounds.getDesktopTaskPosition(finalBounds!!))
+ .isEqualTo(DesktopTaskPosition.Center)
+ }
+
+ @Test
fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -2406,6 +2541,17 @@
private val desktopWallpaperIntent: Intent
get() = Intent(context, DesktopWallpaperActivity::class.java)
+ private fun addFreeformTaskAtPosition(
+ pos: DesktopTaskPosition,
+ stableBounds: Rect,
+ bounds: Rect = DEFAULT_LANDSCAPE_BOUNDS
+ ): RunningTaskInfo {
+ val offset = pos.getTopLeftCoordinates(stableBounds, bounds)
+ val prevTaskBounds = Rect(bounds)
+ prevTaskBounds.offsetTo(offset.x, offset.y)
+ return setUpFreeformTask(bounds = prevTaskBounds)
+ }
+
private fun setUpFreeformTask(
displayId: Int = DEFAULT_DISPLAY,
bounds: Rect? = null
@@ -2434,11 +2580,13 @@
windowingMode: Int = WINDOWING_MODE_FULLSCREEN,
deviceOrientation: Int = ORIENTATION_LANDSCAPE,
screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED,
- shouldLetterbox: Boolean = false
+ shouldLetterbox: Boolean = false,
+ gravity: Int = Gravity.NO_GRAVITY
): RunningTaskInfo {
val task = createFullscreenTask(displayId)
val activityInfo = ActivityInfo()
activityInfo.screenOrientation = screenOrientation
+ activityInfo.windowLayout = ActivityInfo.WindowLayout(0, 0F, 0, 0F, gravity, 0, 0)
with(task) {
topActivityInfo = activityInfo
isResizeable = isResizable
@@ -2479,11 +2627,23 @@
private fun setUpLandscapeDisplay() {
whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG)
whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT)
+ val stableBounds = Rect(0, 0, DISPLAY_DIMENSION_LONG,
+ DISPLAY_DIMENSION_SHORT - Companion.TASKBAR_FRAME_HEIGHT
+ )
+ whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
}
private fun setUpPortraitDisplay() {
whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT)
whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG)
+ val stableBounds = Rect(0, 0, DISPLAY_DIMENSION_SHORT,
+ DISPLAY_DIMENSION_LONG - Companion.TASKBAR_FRAME_HEIGHT
+ )
+ whenever(displayLayout.getStableBoundsForDesktopMode(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
}
private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
@@ -2601,6 +2761,7 @@
const val SECOND_DISPLAY = 2
val STABLE_BOUNDS = Rect(0, 0, 1000, 1000)
const val MAX_TASK_LIMIT = 6
+ private const val TASKBAR_FRAME_HEIGHT = 200
}
}
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 409b877..81e6d07 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
@@ -442,6 +442,27 @@
}
@Test
+ public void testTransitionFilterAnimOverride() {
+ TransitionFilter filter = new TransitionFilter();
+ filter.mRequirements =
+ new TransitionFilter.Requirement[]{new TransitionFilter.Requirement()};
+ filter.mRequirements[0].mCustomAnimation = true;
+ filter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
+
+ final RunningTaskInfo taskInf = createTaskInfo(1);
+ final TransitionInfo openTask = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, taskInf).build();
+ assertFalse(filter.matches(openTask));
+
+ final TransitionInfo.AnimationOptions overOpts =
+ TransitionInfo.AnimationOptions.makeCustomAnimOptions("pakname", 0, 0, 0, true);
+ final TransitionInfo openTaskOpts = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN, taskInf).build();
+ openTaskOpts.getChanges().get(0).setAnimationOptions(overOpts);
+ assertTrue(filter.matches(openTaskOpts));
+ }
+
+ @Test
public void testRegisteredRemoteTransition() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
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 4b069f9..cee9307 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
@@ -20,6 +20,8 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -411,6 +413,80 @@
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+ public void updateRelayoutParams_defaultHeader_addsForceConsumingFlag() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(0);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) != 0).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
+ public void updateRelayoutParams_customHeader_noForceConsumptionFlag() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(
+ APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat((relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING) == 0).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+ public void updateRelayoutParams_header_addsForceConsumingCaptionBar() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(
+ (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) != 0)
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS)
+ public void updateRelayoutParams_handle_skipsForceConsumingCaptionBar() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ final RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(
+ (relayoutParams.mInsetSourceFlags & FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR) == 0)
+ .isTrue();
+ }
+
@DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
public void relayout_fullscreenTask_appliesTransactionImmediately() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -708,7 +784,7 @@
decoration.setOnLeftSnapClickListener(l);
decoration.setOnRightSnapClickListener(l);
decoration.createMaximizeMenu();
- verify(menu).show(any(), any(), any(), mOnMaxMenuHoverChangeListener.capture());
+ verify(menu).show(any(), any(), any(), mOnMaxMenuHoverChangeListener.capture(), any());
}
private void fillRoundedCornersResources(int fillValue) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 2d1bf14..ca6e03c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
+import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
import static android.view.WindowInsets.Type.captionBar;
import static android.view.WindowInsets.Type.mandatorySystemGestures;
import static android.view.WindowInsets.Type.statusBars;
@@ -56,7 +57,6 @@
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
@@ -75,7 +75,6 @@
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
@@ -781,8 +780,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION)
- public void testRelayout_captionInsetForceConsume() {
+ public void testRelayout_captionInsetSourceFlags() {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController)
.getDisplay(Display.DEFAULT_DISPLAY);
@@ -794,11 +792,14 @@
final ActivityManager.RunningTaskInfo taskInfo =
builder.setToken(token).setBounds(new Rect(0, 0, 1000, 1000)).build();
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+ mRelayoutParams.mInsetSourceFlags =
+ FLAG_FORCE_CONSUMING | FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
windowDecor.relayout(taskInfo);
- // Caption inset source should be force-consuming.
+ // Caption inset source should add params' flags.
verify(mMockWindowContainerTransaction).addInsetsSource(eq(token), any(),
- eq(0) /* index */, eq(captionBar()), any(), any(), eq(FLAG_FORCE_CONSUMING));
+ eq(0) /* index */, eq(captionBar()), any(), any(),
+ eq(FLAG_FORCE_CONSUMING | FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
index 3b49055..28b4eb6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainerTest.kt
@@ -29,7 +29,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.kotlin.any
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.eq
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -66,6 +66,8 @@
@Test
fun testReleaseView_ViewRemoved() {
+ val flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
viewContainer = AdditionalSystemViewContainer(
mockContext,
TASK_ID,
@@ -73,9 +75,15 @@
Y,
WIDTH,
HEIGHT,
+ flags,
R.layout.desktop_mode_window_decor_handle_menu
)
- verify(mockWindowManager).addView(eq(mockView), any())
+ verify(mockWindowManager).addView(
+ eq(mockView),
+ argThat {
+ lp -> (lp as WindowManager.LayoutParams).flags == flags
+ }
+ )
viewContainer.releaseView()
verify(mockWindowManager).removeViewImmediate(mockView)
}
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index d415700..010c4e8 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -33,9 +33,6 @@
#define DEBUG_PARCEL 0
static jclass gBitmap_class;
-static jfieldID gBitmap_nativePtr;
-static jmethodID gBitmap_constructorMethodID;
-static jmethodID gBitmap_reinitMethodID;
namespace android {
@@ -183,6 +180,9 @@
void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info,
bool isPremultiplied)
{
+ static jmethodID gBitmap_reinitMethodID =
+ GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
+
// The caller needs to have already set the alpha type properly, so the
// native SkBitmap stays in sync with the Java Bitmap.
assert_premultiplied(info, isPremultiplied);
@@ -194,6 +194,10 @@
jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
int density) {
+ static jmethodID gBitmap_constructorMethodID =
+ GetMethodIDOrDie(env, gBitmap_class,
+ "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
+
bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
// The caller needs to have already set the alpha type properly, so the
@@ -232,11 +236,17 @@
using namespace android;
using namespace android::bitmap;
+static inline jlong getNativePtr(JNIEnv* env, jobject bitmap) {
+ static jfieldID gBitmap_nativePtr =
+ GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
+ return env->GetLongField(bitmap, gBitmap_nativePtr);
+}
+
Bitmap* GraphicsJNI::getNativeBitmap(JNIEnv* env, jobject bitmap) {
SkASSERT(env);
SkASSERT(bitmap);
SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
- jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+ jlong bitmapHandle = getNativePtr(env, bitmap);
LocalScopedBitmap localBitmap(bitmapHandle);
return localBitmap.valid() ? &localBitmap->bitmap() : nullptr;
}
@@ -246,7 +256,7 @@
SkASSERT(env);
SkASSERT(bitmap);
SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class));
- jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr);
+ jlong bitmapHandle = getNativePtr(env, bitmap);
LocalScopedBitmap localBitmap(bitmapHandle);
if (outRowBytes) {
*outRowBytes = localBitmap->rowBytes();
@@ -1269,9 +1279,6 @@
int register_android_graphics_Bitmap(JNIEnv* env)
{
gBitmap_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Bitmap"));
- gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
- gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
- gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
uirenderer::HardwareBufferHelpers::init();
return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
NELEM(gBitmapMethods));
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 8acaf3be..e575dae 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5263,6 +5263,8 @@
* main thread.)
*/
public void setCallback(@Nullable /* MediaCodec. */ Callback cb, @Nullable Handler handler) {
+ boolean setCallbackStallFlag =
+ GetFlag(() -> android.media.codec.Flags.setCallbackStall());
if (cb != null) {
synchronized (mListenerLock) {
EventHandler newHandler = getEventHandlerOn(handler, mCallbackHandler);
@@ -5270,7 +5272,7 @@
// even if we were to extend this to be callable dynamically, it must
// be called when codec is flushed, so no messages are pending.
if (newHandler != mCallbackHandler) {
- if (android.media.codec.Flags.setCallbackStall()) {
+ if (setCallbackStallFlag) {
logAndRun(
"[new handler] removeMessages(SET_CALLBACK)",
() -> {
@@ -5289,7 +5291,7 @@
}
}
} else if (mCallbackHandler != null) {
- if (android.media.codec.Flags.setCallbackStall()) {
+ if (setCallbackStallFlag) {
logAndRun(
"[null handler] removeMessages(SET_CALLBACK)",
() -> {
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 1d6e38d..8a877b8 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -165,7 +165,7 @@
/**
* Creates a new session. The session will automatically be registered with
- * the system but will not be published until {@link #setActive(boolean)
+ * the system, but will not be published until {@link #setActive(boolean)
* setActive(true)} is called. You must call {@link #release()} when
* finished with the session.
* <p>
diff --git a/mms/java/android/telephony/MmsManager.java b/mms/java/android/telephony/MmsManager.java
index b893b45..ac29277 100644
--- a/mms/java/android/telephony/MmsManager.java
+++ b/mms/java/android/telephony/MmsManager.java
@@ -26,6 +26,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import com.android.internal.telephony.IMms;
@@ -69,9 +70,9 @@
return;
}
- iMms.sendMessage(subId, ActivityThread.currentPackageName(), contentUri,
- locationUrl, configOverrides, sentIntent, messageId,
- mContext.getAttributionTag());
+ iMms.sendMessage(subId, /* placeholder callingUser= */ UserHandle.USER_NULL,
+ ActivityThread.currentPackageName(), contentUri, locationUrl,
+ configOverrides, sentIntent, messageId, mContext.getAttributionTag());
} catch (RemoteException e) {
// Ignore it
}
@@ -101,9 +102,9 @@
if (iMms == null) {
return;
}
- iMms.downloadMessage(subId, ActivityThread.currentPackageName(),
- locationUrl, contentUri, configOverrides, downloadedIntent,
- messageId, mContext.getAttributionTag());
+ iMms.downloadMessage(subId, /* placeholder callingUser= */ UserHandle.USER_NULL,
+ ActivityThread.currentPackageName(), locationUrl, contentUri,
+ configOverrides, downloadedIntent, messageId, mContext.getAttributionTag());
} catch (RemoteException e) {
// Ignore it
}
diff --git a/mms/java/com/android/internal/telephony/IMms.aidl b/mms/java/com/android/internal/telephony/IMms.aidl
index 3cdde10..1c75951 100644
--- a/mms/java/com/android/internal/telephony/IMms.aidl
+++ b/mms/java/com/android/internal/telephony/IMms.aidl
@@ -29,6 +29,7 @@
* Send an MMS message with attribution tag.
*
* @param subId the SIM id
+ * @param callingUser user id of the calling app
* @param callingPkg the package name of the calling app
* @param contentUri the content uri from which to read MMS message encoded in standard MMS
* PDU format
@@ -40,7 +41,7 @@
* @param messageId An id that uniquely identifies the message requested to be sent.
* @param attributionTag a tag that attributes the call to a client App.
*/
- void sendMessage(int subId, String callingPkg, in Uri contentUri,
+ void sendMessage(int subId, in int callingUser, String callingPkg, in Uri contentUri,
String locationUrl, in Bundle configOverrides, in PendingIntent sentIntent,
in long messageId, String attributionTag);
@@ -48,6 +49,7 @@
* Download an MMS message using known location and transaction id
*
* @param subId the SIM id
+ * @param callingUser user id of the calling app
* @param callingPkg the package name of the calling app
* @param locationUrl the location URL of the MMS message to be downloaded, usually obtained
* from the MMS WAP push notification
@@ -60,7 +62,7 @@
* @param messageId An id that uniquely identifies the message requested to be downloaded.
* @param attributionTag a tag that attributes the call to a client App.
*/
- void downloadMessage(int subId, String callingPkg, String locationUrl,
+ void downloadMessage(int subId, in int callingUser, String callingPkg, String locationUrl,
in Uri contentUri, in Bundle configOverrides,
in PendingIntent downloadedIntent, in long messageId, String attributionTag);
@@ -82,6 +84,7 @@
/**
* Import a multimedia message into system's MMS store
*
+ * @param callingUser user id of the calling app
* @param callingPkg the package name of the calling app
* @param contentUri the content uri from which to read PDU of the message to import
* @param messageId the optional message id
@@ -90,7 +93,7 @@
* @param read if the message is read
* @return the message URI, null if failed
*/
- Uri importMultimediaMessage(String callingPkg, in Uri contentUri, String messageId,
+ Uri importMultimediaMessage(in int callingUser, String callingPkg, in Uri contentUri, String messageId,
long timestampSecs, boolean seen, boolean read);
/**
@@ -146,11 +149,12 @@
/**
* Add a multimedia message draft to system MMS store
*
+ * @param callingUser user id of the calling app
* @param callingPkg the package name of the calling app
* @param contentUri the content Uri from which to read PDU data of the draft MMS
* @return the URI of the stored draft message
*/
- Uri addMultimediaMessageDraft(String callingPkg, in Uri contentUri);
+ Uri addMultimediaMessageDraft(in int callingUser, String callingPkg, in Uri contentUri);
/**
* Send a system stored MMS message
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 53441c0..0543177 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -11,6 +11,7 @@
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -475,14 +476,26 @@
}
/**
+ * Gets string metadata from Fast Pair customized fields.
+ *
+ * @param bluetoothDevice the BluetoothDevice to get metadata
+ * @return the string metadata
+ */
+ @Nullable
+ public static String getFastPairCustomizedField(
+ @Nullable BluetoothDevice bluetoothDevice, @NonNull String key) {
+ String data = getStringMetaData(bluetoothDevice, METADATA_FAST_PAIR_CUSTOMIZED_FIELDS);
+ return extraTagValue(key, data);
+ }
+
+ /**
* Get URI Bluetooth metadata for extra control
*
* @param bluetoothDevice the BluetoothDevice to get metadata
* @return the URI metadata
*/
public static String getControlUriMetaData(BluetoothDevice bluetoothDevice) {
- String data = getStringMetaData(bluetoothDevice, METADATA_FAST_PAIR_CUSTOMIZED_FIELDS);
- return extraTagValue(KEY_HEARABLE_CONTROL_SLICE, data);
+ return getFastPairCustomizedField(bluetoothDevice, KEY_HEARABLE_CONTROL_SLICE);
}
/**
@@ -835,9 +848,9 @@
/** Get primary device group id in broadcast. */
@WorkerThread
- public static int getPrimaryGroupIdForBroadcast(@NonNull Context context) {
+ public static int getPrimaryGroupIdForBroadcast(@NonNull ContentResolver contentResolver) {
return Settings.Secure.getInt(
- context.getContentResolver(),
+ contentResolver,
getPrimaryGroupIdUriForBroadcast(),
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
@@ -846,9 +859,13 @@
@Nullable
@WorkerThread
public static CachedBluetoothDevice getSecondaryDeviceForBroadcast(
- @NonNull Context context, @Nullable LocalBluetoothManager localBtManager) {
+ @NonNull ContentResolver contentResolver,
+ @Nullable LocalBluetoothManager localBtManager) {
if (localBtManager == null) return null;
- int primaryGroupId = getPrimaryGroupIdForBroadcast(context);
+ LocalBluetoothLeBroadcast broadcast =
+ localBtManager.getProfileManager().getLeAudioBroadcastProfile();
+ if (!broadcast.isEnabled(null)) return null;
+ int primaryGroupId = getPrimaryGroupIdForBroadcast(contentResolver);
if (primaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) return null;
LocalBluetoothLeBroadcastAssistant assistant =
localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
@@ -866,4 +883,4 @@
}
return null;
}
-}
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
new file mode 100644
index 0000000..0bcf7fe
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.bluetooth
+
+import android.bluetooth.BluetoothLeBroadcast
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import com.android.internal.util.ConcurrentUtils
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** [Flow] for [BluetoothLeBroadcast.Callback] source start/stop events */
+val LocalBluetoothLeBroadcast.onBroadcastStartedOrStopped: Flow<Unit>
+ get() =
+ callbackFlow {
+ val listener =
+ object : BluetoothLeBroadcast.Callback {
+ override fun onBroadcastStarted(reason: Int, broadcastId: Int) {
+ launch { trySend(Unit) }
+ }
+
+ override fun onBroadcastStartFailed(reason: Int) {
+ launch { trySend(Unit) }
+ }
+
+ override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
+ launch { trySend(Unit) }
+ }
+
+ override fun onBroadcastStopFailed(reason: Int) {
+ launch { trySend(Unit) }
+ }
+
+ override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
+
+ override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
+
+ override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
+
+ override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
+
+ override fun onBroadcastMetadataChanged(
+ broadcastId: Int,
+ metadata: BluetoothLeBroadcastMetadata
+ ) {}
+ }
+ registerServiceCallBack(
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ listener,
+ )
+ awaitClose { unregisterServiceCallBack(listener) }
+ }
+ .buffer(capacity = Channel.CONFLATED)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java
index 91c1a59..eefceae 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java
@@ -21,6 +21,9 @@
import android.os.Parcelable;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
/** A data class representing the state of an action/switch preference. */
public class ActionSwitchPreferenceState extends DeviceSettingPreferenceState
@@ -133,4 +136,15 @@
public Bundle getExtras() {
return mExtras;
}
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof ActionSwitchPreferenceState other)) return false;
+ return mChecked == other.mChecked;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mChecked);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java
index 52e520e..bfd1d1a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java
@@ -21,6 +21,7 @@
import android.os.Parcelable;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.util.Objects;
@@ -134,4 +135,15 @@
public Bundle getExtras() {
return mExtras;
}
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof DeviceInfo other)) return false;
+ return Objects.equals(mBluetoothAddress, other.getBluetoothAddress());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBluetoothAddress);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java
index 63fd4eb..b505f27 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java
@@ -21,6 +21,7 @@
import android.os.Parcelable;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.util.Objects;
@@ -162,4 +163,16 @@
public Bundle getExtras() {
return mExtras;
}
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof DeviceSettingState other)) return false;
+ return mSettingId == other.mSettingId
+ && Objects.equals(mPreferenceState, other.mPreferenceState);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSettingId, mPreferenceState);
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
copy to packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
index 37c9552..647611e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.settingslib.bluetooth.devicesettings;
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
+import com.android.settingslib.bluetooth.devicesettings.DeviceInfo;
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig;
-val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
+interface IDeviceSettingsConfigProviderService {
+ DeviceSettingsConfig getDeviceSettingsConfig(in DeviceInfo device);
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java
index 239df0b..d9ce4c5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java
@@ -21,6 +21,9 @@
import android.os.Parcelable;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
/** A data class representing a multi-toggle preference state. */
public class MultiTogglePreferenceState extends DeviceSettingPreferenceState implements Parcelable {
@@ -123,4 +126,15 @@
public Bundle getExtras() {
return mExtras;
}
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof MultiTogglePreferenceState other)) return false;
+ return mState == other.mState;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mState);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
new file mode 100644
index 0000000..9ff5c43
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.bluetooth.devicesettings.data.repository
+
+import android.bluetooth.BluetoothAdapter
+import android.content.Context
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingPreferenceState
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+
+/** Provides functionality to control bluetooth device settings. */
+interface DeviceSettingRepository {
+ /** Gets config for the bluetooth device, returns null if failed. */
+ suspend fun getDeviceSettingsConfig(cachedDevice: CachedBluetoothDevice): DeviceSettingsConfig?
+
+ /** Gets all device settings for the bluetooth device. */
+ fun getDeviceSettingList(
+ cachedDevice: CachedBluetoothDevice,
+ ): Flow<List<DeviceSetting>?>
+
+ /** Gets device setting for the bluetooth device. */
+ fun getDeviceSetting(
+ cachedDevice: CachedBluetoothDevice,
+ @DeviceSettingId settingId: Int
+ ): Flow<DeviceSetting?>
+
+ /** Updates device setting for the bluetooth device. */
+ suspend fun updateDeviceSettingState(
+ cachedDevice: CachedBluetoothDevice,
+ @DeviceSettingId deviceSettingId: Int,
+ deviceSettingPreferenceState: DeviceSettingPreferenceState,
+ )
+}
+
+class DeviceSettingRepositoryImpl(
+ private val context: Context,
+ private val bluetoothAdaptor: BluetoothAdapter,
+ private val coroutineScope: CoroutineScope,
+ private val backgroundCoroutineContext: CoroutineContext,
+) : DeviceSettingRepository {
+ private val deviceSettings =
+ ConcurrentHashMap<CachedBluetoothDevice, DeviceSettingServiceConnection>()
+
+ override suspend fun getDeviceSettingsConfig(
+ cachedDevice: CachedBluetoothDevice
+ ): DeviceSettingsConfig? = createConnectionIfAbsent(cachedDevice).getDeviceSettingsConfig()
+
+ override fun getDeviceSettingList(
+ cachedDevice: CachedBluetoothDevice
+ ): Flow<List<DeviceSetting>?> = createConnectionIfAbsent(cachedDevice).getDeviceSettingList()
+
+ override fun getDeviceSetting(
+ cachedDevice: CachedBluetoothDevice,
+ settingId: Int
+ ): Flow<DeviceSetting?> = createConnectionIfAbsent(cachedDevice).getDeviceSetting(settingId)
+
+ override suspend fun updateDeviceSettingState(
+ cachedDevice: CachedBluetoothDevice,
+ @DeviceSettingId deviceSettingId: Int,
+ deviceSettingPreferenceState: DeviceSettingPreferenceState,
+ ) =
+ createConnectionIfAbsent(cachedDevice)
+ .updateDeviceSettings(deviceSettingId, deviceSettingPreferenceState)
+
+ private fun createConnectionIfAbsent(
+ cachedDevice: CachedBluetoothDevice
+ ): DeviceSettingServiceConnection =
+ deviceSettings.computeIfAbsent(cachedDevice) {
+ DeviceSettingServiceConnection(
+ cachedDevice,
+ context,
+ bluetoothAdaptor,
+ coroutineScope,
+ backgroundCoroutineContext,
+ )
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
new file mode 100644
index 0000000..d6b2862
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -0,0 +1,285 @@
+/*
+ * 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.bluetooth.devicesettings.data.repository
+
+import android.bluetooth.BluetoothAdapter
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import com.android.internal.util.ConcurrentUtils
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.DeviceInfo
+import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingPreferenceState
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingState
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsConfigProviderService
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsProviderService
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicReference
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emitAll
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class DeviceSettingServiceConnection(
+ private val cachedDevice: CachedBluetoothDevice,
+ private val context: Context,
+ private val bluetoothAdaptor: BluetoothAdapter,
+ private val coroutineScope: CoroutineScope,
+ private val backgroundCoroutineContext: CoroutineContext,
+) {
+ data class EndPoint(
+ private val packageName: String,
+ private val className: String?,
+ private val intentAction: String,
+ ) {
+ fun toIntent(): Intent =
+ Intent().apply {
+ if (className.isNullOrBlank()) {
+ setPackage(packageName)
+ } else {
+ setClassName(packageName, className)
+ }
+ setAction(intentAction)
+ }
+ }
+
+ private var config = AtomicReference<DeviceSettingsConfig?>(null)
+ private var idToSetting = AtomicReference<Flow<Map<Int, DeviceSetting>>?>(null)
+
+ /** Gets [DeviceSettingsConfig] for the device, return null when failed. */
+ suspend fun getDeviceSettingsConfig(): DeviceSettingsConfig? =
+ config.computeIfAbsent {
+ getConfigServiceBindingIntent(cachedDevice)
+ .flatMapLatest { getService(it) }
+ .map { it?.let { IDeviceSettingsConfigProviderService.Stub.asInterface(it) } }
+ .map {
+ it?.getDeviceSettingsConfig(
+ deviceInfo { setBluetoothAddress(cachedDevice.address) }
+ )
+ }
+ .first()
+ }
+
+ /** Gets all device settings for the device. */
+ fun getDeviceSettingList(): Flow<List<DeviceSetting>> =
+ getSettingIdToItemMapping().map { it.values.toList() }
+
+ /** Gets the device settings with the ID for the device. */
+ fun getDeviceSetting(@DeviceSettingId deviceSettingId: Int): Flow<DeviceSetting?> =
+ getSettingIdToItemMapping().map { it[deviceSettingId] }
+
+ /** Updates the device setting state for the device. */
+ suspend fun updateDeviceSettings(
+ @DeviceSettingId deviceSettingId: Int,
+ deviceSettingPreferenceState: DeviceSettingPreferenceState,
+ ) {
+ getDeviceSettingsConfig()?.let { config ->
+ (config.mainContentItems + config.moreSettingsItems)
+ .find { it.settingId == deviceSettingId }
+ ?.let {
+ getSettingsProviderServices()
+ ?.get(EndPoint(it.packageName, it.className, it.intentAction))
+ ?.filterNotNull()
+ ?.first()
+ }
+ ?.updateDeviceSettings(
+ deviceInfo { setBluetoothAddress(cachedDevice.address) },
+ DeviceSettingState.Builder()
+ .setSettingId(deviceSettingId)
+ .setPreferenceState(deviceSettingPreferenceState)
+ .build()
+ )
+ }
+ }
+
+ private suspend fun getSettingsProviderServices():
+ Map<EndPoint, StateFlow<IDeviceSettingsProviderService?>>? =
+ getDeviceSettingsConfig()
+ ?.let { config ->
+ (config.mainContentItems + config.moreSettingsItems).map {
+ EndPoint(
+ packageName = it.packageName,
+ className = it.className,
+ intentAction = it.intentAction
+ )
+ }
+ }
+ ?.distinct()
+ ?.associateBy(
+ { it },
+ { endpoint ->
+ services.computeIfAbsent(endpoint) {
+ getService(endpoint.toIntent())
+ .map { service ->
+ IDeviceSettingsProviderService.Stub.asInterface(service)
+ }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+ }
+ }
+ )
+
+ private fun getSettingIdToItemMapping(): Flow<Map<Int, DeviceSetting>> =
+ idToSetting.computeIfAbsent {
+ flow {
+ getSettingsProviderServices()
+ ?.values
+ ?.map {
+ it.flatMapLatest { service ->
+ if (service != null) {
+ getDeviceSettingsFromService(cachedDevice, service)
+ } else {
+ flowOf(emptyList())
+ }
+ }
+ }
+ ?.let { items -> combine(items) { it.toList().flatten() } }
+ ?.map { items -> items.associateBy { it.settingId } }
+ ?.let { emitAll(it) }
+ }
+ .shareIn(
+ scope = coroutineScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1
+ )
+ }!!
+
+ private fun getDeviceSettingsFromService(
+ cachedDevice: CachedBluetoothDevice,
+ service: IDeviceSettingsProviderService
+ ): Flow<List<DeviceSetting>> {
+ return callbackFlow {
+ val listener =
+ object : IDeviceSettingsListener.Stub() {
+ override fun onDeviceSettingsChanged(settings: List<DeviceSetting>) {
+ launch { send(settings) }
+ }
+ }
+ val deviceInfo = deviceInfo { setBluetoothAddress(cachedDevice.address) }
+ service.registerDeviceSettingsListener(deviceInfo, listener)
+ awaitClose { service.unregisterDeviceSettingsListener(deviceInfo, listener) }
+ }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyList())
+ }
+
+ private fun getService(intent: Intent): Flow<IBinder?> {
+ return callbackFlow {
+ val serviceConnection =
+ object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ launch { send(service) }
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ launch { send(null) }
+ }
+ }
+ if (!context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)) {
+ launch { send(null) }
+ }
+ awaitClose { context.unbindService(serviceConnection) }
+ }
+ }
+
+ private fun getConfigServiceBindingIntent(cachedDevice: CachedBluetoothDevice): Flow<Intent> {
+ return callbackFlow {
+ val listener =
+ BluetoothAdapter.OnMetadataChangedListener { device, key, _ ->
+ if (
+ key == METADATA_FAST_PAIR_CUSTOMIZED_FIELDS &&
+ cachedDevice.device == device
+ ) {
+ launch { tryGetEndpointFromMetadata(cachedDevice)?.let { send(it) } }
+ }
+ }
+ bluetoothAdaptor.addOnMetadataChangedListener(
+ cachedDevice.device,
+ ConcurrentUtils.DIRECT_EXECUTOR,
+ listener,
+ )
+ awaitClose {
+ bluetoothAdaptor.removeOnMetadataChangedListener(cachedDevice.device, listener)
+ }
+ }
+ .onStart { tryGetEndpointFromMetadata(cachedDevice)?.let { emit(it) } }
+ .distinctUntilChanged()
+ .map { it.toIntent() }
+ .flowOn(backgroundCoroutineContext)
+ }
+
+ private suspend fun tryGetEndpointFromMetadata(cachedDevice: CachedBluetoothDevice): EndPoint? =
+ withContext(backgroundCoroutineContext) {
+ val packageName =
+ BluetoothUtils.getFastPairCustomizedField(
+ cachedDevice.device,
+ CONFIG_SERVICE_PACKAGE_NAME,
+ ) ?: return@withContext null
+ val className =
+ BluetoothUtils.getFastPairCustomizedField(
+ cachedDevice.device,
+ CONFIG_SERVICE_CLASS_NAME
+ ) ?: return@withContext null
+ val intentAction =
+ BluetoothUtils.getFastPairCustomizedField(
+ cachedDevice.device,
+ CONFIG_SERVICE_INTENT_ACTION
+ ) ?: return@withContext null
+ EndPoint(packageName, className, intentAction)
+ }
+
+ private inline fun <T> AtomicReference<T?>.computeIfAbsent(producer: () -> T): T? =
+ get() ?: producer().let { compareAndExchange(null, it) ?: it }
+
+ private inline fun deviceInfo(block: DeviceInfo.Builder.() -> Unit): DeviceInfo {
+ return DeviceInfo.Builder().apply { block() }.build()
+ }
+
+ companion object {
+ const val METADATA_FAST_PAIR_CUSTOMIZED_FIELDS: Int = 25
+ const val CONFIG_SERVICE_PACKAGE_NAME = "DEVICE_SETTINGS_CONFIG_PACKAGE_NAME"
+ const val CONFIG_SERVICE_CLASS_NAME = "DEVICE_SETTINGS_CONFIG_CLASS"
+ const val CONFIG_SERVICE_INTENT_ACTION = "DEVICE_SETTINGS_CONFIG_ACTION"
+
+ val services = ConcurrentHashMap<EndPoint, StateFlow<IDeviceSettingsProviderService?>>()
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 49b974f..87ab6b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -73,15 +73,22 @@
}
fun activateMode(id: String) {
- val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
- removeMode(id)
- mutableModesFlow.value += TestModeBuilder(oldMode).setActive(true).build()
+ updateModeActiveState(id = id, isActive = true)
}
fun deactivateMode(id: String) {
- val oldMode = mutableModesFlow.value.find { it.id == id } ?: return
- removeMode(id)
- mutableModesFlow.value += TestModeBuilder(oldMode).setActive(false).build()
+ updateModeActiveState(id = id, isActive = false)
+ }
+
+ // Update the active state while maintaining the mode's position in the list
+ private fun updateModeActiveState(id: String, isActive: Boolean) {
+ val modes = mutableModesFlow.value.toMutableList()
+ val index = modes.indexOfFirst { it.id == id }
+ if (index < 0) {
+ throw IllegalArgumentException("mode $id not found")
+ }
+ modes[index] = TestModeBuilder(modes[index]).setActive(isActive).build()
+ mutableModesFlow.value = modes
}
}
@@ -101,7 +108,8 @@
suppressedVisualEffects,
state,
priorityConversationSenders,
- ))
+ )
+ )
private fun newMode(id: String, active: Boolean = false): ZenMode {
return TestModeBuilder().setId(id).setName("Mode $id").setActive(active).build()
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 2f7cdd6..a06f084 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -16,6 +16,9 @@
package com.android.settingslib.notification.modes;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN;
+import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
+
import android.app.AutomaticZenRule;
import android.app.NotificationManager;
import android.content.ComponentName;
@@ -144,8 +147,15 @@
}
public TestModeBuilder setEnabled(boolean enabled) {
+ return setEnabled(enabled, /* byUser= */ false);
+ }
+
+ public TestModeBuilder setEnabled(boolean enabled, boolean byUser) {
mRule.setEnabled(enabled);
mConfigZenRule.enabled = enabled;
+ if (!enabled) {
+ mConfigZenRule.disabledOrigin = byUser ? UPDATE_ORIGIN_USER : UPDATE_ORIGIN_UNKNOWN;
+ }
return this;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
index 960df63..271d5c4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java
@@ -35,7 +35,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.content.res.AppCompatResources;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.Futures;
@@ -104,7 +103,7 @@
return context.getDrawable(iconResId);
} else {
Context appContext = context.createPackageContext(pkg, 0);
- Drawable appDrawable = AppCompatResources.getDrawable(appContext, iconResId);
+ Drawable appDrawable = appContext.getDrawable(iconResId);
return getMonochromeIconIfPresent(appDrawable);
}
})).catching(Exception.class, ex -> {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 03a2b75..990a2d4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -298,6 +298,10 @@
return mIsManualDnd;
}
+ public boolean isEnabled() {
+ return mRule.isEnabled();
+ }
+
public boolean isActive() {
return mStatus == Status.ENABLED_AND_ACTIVE;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
index eb33a7a..99d5891 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
@@ -19,21 +19,18 @@
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothCsipSetCoordinator
import android.bluetooth.BluetoothDevice
-import android.bluetooth.BluetoothLeBroadcast
-import android.bluetooth.BluetoothLeBroadcastMetadata
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothVolumeControl
import android.content.ContentResolver
-import android.content.Context
import android.database.ContentObserver
import android.provider.Settings
import androidx.annotation.IntRange
import com.android.internal.util.ConcurrentUtils
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.onBroadcastStartedOrStopped
import com.android.settingslib.bluetooth.onProfileConnectionStateChanged
import com.android.settingslib.bluetooth.onSourceConnectedOrRemoved
-import com.android.settingslib.flags.Flags
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
import kotlin.coroutines.CoroutineContext
@@ -41,10 +38,10 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
@@ -65,6 +62,9 @@
/** Whether the device is in audio sharing. */
val inAudioSharing: Flow<Boolean>
+ /** The primary headset groupId in audio sharing. */
+ val primaryGroupId: StateFlow<Int>
+
/** The secondary headset groupId in audio sharing. */
val secondaryGroupId: StateFlow<Int>
@@ -85,63 +85,18 @@
@OptIn(ExperimentalCoroutinesApi::class)
class AudioSharingRepositoryImpl(
- private val context: Context,
private val contentResolver: ContentResolver,
- private val btManager: LocalBluetoothManager?,
+ private val btManager: LocalBluetoothManager,
private val coroutineScope: CoroutineScope,
private val backgroundCoroutineContext: CoroutineContext,
) : AudioSharingRepository {
override val inAudioSharing: Flow<Boolean> =
- if (Flags.enableLeAudioSharing()) {
- btManager?.profileManager?.leAudioBroadcastProfile?.let { leBroadcast ->
- callbackFlow {
- val listener =
- object : BluetoothLeBroadcast.Callback {
- override fun onBroadcastStarted(reason: Int, broadcastId: Int) {
- launch { send(isBroadcasting()) }
- }
-
- override fun onBroadcastStartFailed(reason: Int) {
- launch { send(isBroadcasting()) }
- }
-
- override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
- launch { send(isBroadcasting()) }
- }
-
- override fun onBroadcastStopFailed(reason: Int) {
- launch { send(isBroadcasting()) }
- }
-
- override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
-
- override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
-
- override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
-
- override fun onBroadcastUpdateFailed(
- reason: Int,
- broadcastId: Int
- ) {}
-
- override fun onBroadcastMetadataChanged(
- broadcastId: Int,
- metadata: BluetoothLeBroadcastMetadata
- ) {}
- }
-
- leBroadcast.registerServiceCallBack(
- ConcurrentUtils.DIRECT_EXECUTOR,
- listener,
- )
- awaitClose { leBroadcast.unregisterServiceCallBack(listener) }
- }
- .onStart { emit(isBroadcasting()) }
- .flowOn(backgroundCoroutineContext)
- } ?: flowOf(false)
- } else {
- flowOf(false)
- }
+ btManager.profileManager.leAudioBroadcastProfile?.let { broadcast ->
+ broadcast.onBroadcastStartedOrStopped
+ .map { isBroadcasting() }
+ .onStart { emit(isBroadcasting()) }
+ .flowOn(backgroundCoroutineContext)
+ } ?: flowOf(false)
private val primaryChange: Flow<Unit> = callbackFlow {
val callback =
@@ -157,35 +112,35 @@
awaitClose { contentResolver.unregisterContentObserver(callback) }
}
+ override val primaryGroupId: StateFlow<Int> =
+ primaryChange
+ .map { BluetoothUtils.getPrimaryGroupIdForBroadcast(contentResolver) }
+ .onStart { emit(BluetoothUtils.getPrimaryGroupIdForBroadcast(contentResolver)) }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ BluetoothUtils.getPrimaryGroupIdForBroadcast(contentResolver))
+
override val secondaryGroupId: StateFlow<Int> =
- if (Flags.volumeDialogAudioSharingFix()) {
- merge(
- btManager
- ?.profileManager
- ?.leAudioBroadcastAssistantProfile
- ?.onSourceConnectedOrRemoved
- ?.map { getSecondaryGroupId() } ?: emptyFlow(),
- btManager
- ?.eventManager
- ?.onProfileConnectionStateChanged
- ?.filter { profileConnection ->
- profileConnection.state == BluetoothAdapter.STATE_DISCONNECTED &&
- profileConnection.bluetoothProfile ==
- BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
- }
- ?.map { getSecondaryGroupId() } ?: emptyFlow(),
- primaryChange.map { getSecondaryGroupId() })
- .onStart { emit(getSecondaryGroupId()) }
- .distinctUntilChanged()
- .flowOn(backgroundCoroutineContext)
- } else {
- emptyFlow()
- }
+ merge(
+ btManager.profileManager.leAudioBroadcastAssistantProfile
+ ?.onSourceConnectedOrRemoved
+ ?.map { getSecondaryGroupId() } ?: emptyFlow(),
+ btManager.eventManager.onProfileConnectionStateChanged
+ .filter { profileConnection ->
+ profileConnection.state == BluetoothAdapter.STATE_DISCONNECTED &&
+ profileConnection.bluetoothProfile ==
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+ }
+ .map { getSecondaryGroupId() },
+ primaryGroupId.map { getSecondaryGroupId() })
+ .onStart { emit(getSecondaryGroupId()) }
+ .flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), getSecondaryGroupId())
override val volumeMap: StateFlow<GroupIdToVolumes> =
- if (Flags.volumeDialogAudioSharingFix()) {
- btManager?.profileManager?.volumeControlProfile?.let { volumeControl ->
+ (btManager.profileManager.volumeControlProfile?.let { volumeControl ->
inAudioSharing.flatMapLatest { isSharing ->
if (isSharing) {
callbackFlow {
@@ -210,50 +165,55 @@
.runningFold(emptyMap<Int, Int>()) { acc, value ->
val groupId =
BluetoothUtils.getGroupId(
- btManager.cachedDeviceManager?.findDevice(value.first))
+ btManager.cachedDeviceManager.findDevice(value.first))
if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
acc + Pair(groupId, value.second)
} else {
acc
}
}
- .distinctUntilChanged()
.flowOn(backgroundCoroutineContext)
} else {
emptyFlow()
}
}
- } ?: emptyFlow()
- } else {
- emptyFlow()
- }
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyMap())
+ } ?: emptyFlow())
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), emptyMap())
override suspend fun setSecondaryVolume(
@IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
volume: Int
) {
withContext(backgroundCoroutineContext) {
- if (Flags.volumeDialogAudioSharingFix()) {
- btManager?.profileManager?.volumeControlProfile?.let {
- // Find secondary headset and set volume.
- val cachedDevice =
- BluetoothUtils.getSecondaryDeviceForBroadcast(context, btManager)
- if (cachedDevice != null) {
- it.setDeviceVolume(cachedDevice.device, volume, /* isGroupOp= */ true)
- }
+ btManager.profileManager.volumeControlProfile?.let {
+ // Find secondary headset and set volume.
+ val cachedDevice =
+ BluetoothUtils.getSecondaryDeviceForBroadcast(contentResolver, btManager)
+ if (cachedDevice != null) {
+ it.setDeviceVolume(cachedDevice.device, volume, /* isGroupOp= */ true)
}
}
}
}
- private fun isBroadcasting(): Boolean {
- return Flags.enableLeAudioSharing() &&
- (btManager?.profileManager?.leAudioBroadcastProfile?.isEnabled(null) ?: false)
- }
+ private fun isBroadcasting(): Boolean =
+ btManager.profileManager.leAudioBroadcastProfile?.isEnabled(null) ?: false
- private fun getSecondaryGroupId(): Int {
- return BluetoothUtils.getGroupId(
- BluetoothUtils.getSecondaryDeviceForBroadcast(context, btManager))
- }
+ private fun getSecondaryGroupId(): Int =
+ BluetoothUtils.getGroupId(
+ BluetoothUtils.getSecondaryDeviceForBroadcast(contentResolver, btManager))
+}
+
+class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
+ override val inAudioSharing: Flow<Boolean> = flowOf(false)
+ override val primaryGroupId: StateFlow<Int> =
+ MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
+ override val secondaryGroupId: StateFlow<Int> =
+ MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
+ override val volumeMap: StateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
+
+ override suspend fun setSecondaryVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ volume: Int
+ ) {}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryEmptyImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryEmptyImplTest.kt
new file mode 100644
index 0000000..2601592
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryEmptyImplTest.kt
@@ -0,0 +1,174 @@
+/*
+ * 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 android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothLeBroadcastReceiveState
+import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothEventManager
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.settingslib.bluetooth.VolumeControlProfile
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AudioSharingRepositoryEmptyImplTest {
+ @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
+ @Mock private lateinit var btManager: LocalBluetoothManager
+
+ @Mock private lateinit var profileManager: LocalBluetoothProfileManager
+
+ @Mock private lateinit var broadcast: LocalBluetoothLeBroadcast
+
+ @Mock private lateinit var assistant: LocalBluetoothLeBroadcastAssistant
+
+ @Mock private lateinit var volumeControl: VolumeControlProfile
+
+ @Mock private lateinit var eventManager: BluetoothEventManager
+
+ @Mock private lateinit var deviceManager: CachedBluetoothDeviceManager
+
+ @Mock private lateinit var device1: BluetoothDevice
+
+ @Mock private lateinit var device2: BluetoothDevice
+
+ @Mock private lateinit var cachedDevice1: CachedBluetoothDevice
+
+ @Mock private lateinit var cachedDevice2: CachedBluetoothDevice
+
+ @Mock private lateinit var receiveState: BluetoothLeBroadcastReceiveState
+
+ private val testScope = TestScope()
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private lateinit var underTest: AudioSharingRepository
+
+ @Before
+ fun setup() {
+ `when`(btManager.profileManager).thenReturn(profileManager)
+ `when`(profileManager.leAudioBroadcastProfile).thenReturn(broadcast)
+ `when`(profileManager.leAudioBroadcastAssistantProfile).thenReturn(assistant)
+ `when`(profileManager.volumeControlProfile).thenReturn(volumeControl)
+ `when`(btManager.eventManager).thenReturn(eventManager)
+ `when`(btManager.cachedDeviceManager).thenReturn(deviceManager)
+ `when`(broadcast.isEnabled(null)).thenReturn(true)
+ `when`(cachedDevice1.groupId).thenReturn(TEST_GROUP_ID1)
+ `when`(cachedDevice1.device).thenReturn(device1)
+ `when`(deviceManager.findDevice(device1)).thenReturn(cachedDevice1)
+ `when`(cachedDevice2.groupId).thenReturn(TEST_GROUP_ID2)
+ `when`(cachedDevice2.device).thenReturn(device2)
+ `when`(deviceManager.findDevice(device2)).thenReturn(cachedDevice2)
+ `when`(receiveState.bisSyncState).thenReturn(arrayListOf(TEST_RECEIVE_STATE_CONTENT))
+ `when`(assistant.getAllSources(any())).thenReturn(listOf(receiveState))
+ underTest = AudioSharingRepositoryEmptyImpl()
+ }
+
+ @Test
+ fun inAudioSharing_returnFalse() {
+ testScope.runTest {
+ val states = mutableListOf<Boolean?>()
+ underTest.inAudioSharing.onEach { states.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ Truth.assertThat(states).containsExactly(false)
+ verify(broadcast, never()).registerServiceCallBack(any(), any())
+ verify(broadcast, never()).isEnabled(any())
+ }
+ }
+
+ @Test
+ fun secondaryGroupIdChange_returnFalse() {
+ testScope.runTest {
+ val groupIds = mutableListOf<Int?>()
+ underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ Truth.assertThat(groupIds).containsExactly(TEST_GROUP_ID_INVALID)
+ verify(assistant, never()).registerServiceCallBack(any(), any())
+ verify(eventManager, never()).registerCallback(any())
+ }
+ }
+
+ @Test
+ fun volumeMapChange_returnFalse() {
+ testScope.runTest {
+ val volumeMaps = mutableListOf<GroupIdToVolumes?>()
+ underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ Truth.assertThat(volumeMaps).containsExactly(emptyMap<Int, Int>())
+ verify(broadcast, never()).registerServiceCallBack(any(), any())
+ verify(volumeControl, never()).registerCallback(any(), any())
+ }
+ }
+
+ @Test
+ fun setSecondaryVolume_doNothing() {
+ testScope.runTest {
+ Settings.Secure.putInt(
+ context.contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID2)
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+ underTest.setSecondaryVolume(TEST_VOLUME1)
+
+ runCurrent()
+ verify(volumeControl, never()).setDeviceVolume(any(), anyInt(), anyBoolean())
+ }
+ }
+
+ private companion object {
+ const val TEST_GROUP_ID_INVALID = -1
+ const val TEST_GROUP_ID1 = 1
+ const val TEST_GROUP_ID2 = 2
+ const val TEST_RECEIVE_STATE_CONTENT = 1L
+ const val TEST_VOLUME1 = 10
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
index 000664d..c54a2e4 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
@@ -26,8 +26,6 @@
import android.content.ContentResolver
import android.content.Context
import android.database.ContentObserver
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import androidx.test.core.app.ApplicationProvider
@@ -43,7 +41,6 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
import com.android.settingslib.bluetooth.VolumeControlProfile
-import com.android.settingslib.flags.Flags
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -57,14 +54,12 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@@ -100,8 +95,6 @@
@Mock private lateinit var receiveState: BluetoothLeBroadcastReceiveState
- @Mock private lateinit var contentResolver: ContentResolver
-
@Captor
private lateinit var broadcastCallbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
@@ -118,6 +111,7 @@
private val testScope = TestScope()
private val context: Context = ApplicationProvider.getApplicationContext()
+ @Spy private val contentResolver: ContentResolver = context.contentResolver
private lateinit var underTest: AudioSharingRepository
@Before
@@ -137,9 +131,12 @@
`when`(deviceManager.findDevice(device2)).thenReturn(cachedDevice2)
`when`(receiveState.bisSyncState).thenReturn(arrayListOf(TEST_RECEIVE_STATE_CONTENT))
`when`(assistant.getAllSources(any())).thenReturn(listOf(receiveState))
+ Settings.Secure.putInt(
+ contentResolver,
+ BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
+ TEST_GROUP_ID_INVALID)
underTest =
AudioSharingRepositoryImpl(
- context,
contentResolver,
btManager,
testScope.backgroundScope,
@@ -148,7 +145,6 @@
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
fun audioSharingStateChange_emitValues() {
testScope.runTest {
val states = mutableListOf<Boolean?>()
@@ -164,21 +160,22 @@
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING)
- fun audioSharingFlagOff_returnFalse() {
+ fun primaryGroupIdChange_emitValues() {
testScope.runTest {
- val states = mutableListOf<Boolean?>()
- underTest.inAudioSharing.onEach { states.add(it) }.launchIn(backgroundScope)
+ val groupIds = mutableListOf<Int?>()
+ underTest.primaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ triggerContentObserverChange()
runCurrent()
- Truth.assertThat(states).containsExactly(false)
- verify(broadcast, never()).registerServiceCallBack(any(), any())
- verify(broadcast, never()).isEnabled(any())
+ Truth.assertThat(groupIds)
+ .containsExactly(
+ TEST_GROUP_ID_INVALID,
+ TEST_GROUP_ID2)
}
}
@Test
- @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
fun secondaryGroupIdChange_emitValues() {
testScope.runTest {
val groupIds = mutableListOf<Int?>()
@@ -214,21 +211,6 @@
}
@Test
- @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
- fun secondaryGroupIdChange_audioSharingFlagOff_returnFalse() {
- testScope.runTest {
- val groupIds = mutableListOf<Int?>()
- underTest.secondaryGroupId.onEach { groupIds.add(it) }.launchIn(backgroundScope)
- runCurrent()
-
- Truth.assertThat(groupIds).containsExactly(TEST_GROUP_ID_INVALID)
- verify(assistant, never()).registerServiceCallBack(any(), any())
- verify(eventManager, never()).registerCallback(any())
- }
- }
-
- @Test
- @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
fun volumeMapChange_emitValues() {
testScope.runTest {
val volumeMaps = mutableListOf<GroupIdToVolumes?>()
@@ -252,25 +234,10 @@
}
@Test
- @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
- fun volumeMapChange_audioSharingFlagOff_returnFalse() {
- testScope.runTest {
- val volumeMaps = mutableListOf<GroupIdToVolumes?>()
- underTest.volumeMap.onEach { volumeMaps.add(it) }.launchIn(backgroundScope)
- runCurrent()
-
- Truth.assertThat(volumeMaps).isEmpty()
- verify(broadcast, never()).registerServiceCallBack(any(), any())
- verify(volumeControl, never()).registerCallback(any(), any())
- }
- }
-
- @Test
- @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
fun setSecondaryVolume_setValue() {
testScope.runTest {
Settings.Secure.putInt(
- context.contentResolver,
+ contentResolver,
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
TEST_GROUP_ID2)
`when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
@@ -281,22 +248,6 @@
}
}
- @Test
- @DisableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
- fun setSecondaryVolume_audioSharingFlagOff_doNothing() {
- testScope.runTest {
- Settings.Secure.putInt(
- context.contentResolver,
- BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
- TEST_GROUP_ID2)
- `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
- underTest.setSecondaryVolume(TEST_VOLUME1)
-
- runCurrent()
- verify(volumeControl, never()).setDeviceVolume(any(), anyInt(), anyBoolean())
- }
- }
-
private fun triggerAudioSharingStateChange(
type: TriggerType,
broadcastAction: BluetoothLeBroadcast.Callback.() -> Unit
@@ -317,7 +268,7 @@
private fun triggerSourceAdded() {
verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
Settings.Secure.putInt(
- context.contentResolver,
+ contentResolver,
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
TEST_GROUP_ID1)
`when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
@@ -328,7 +279,7 @@
verify(assistant).registerServiceCallBack(any(), assistantCallbackCaptor.capture())
`when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
Settings.Secure.putInt(
- context.contentResolver,
+ contentResolver,
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
TEST_GROUP_ID1)
assistantCallbackCaptor.value.sourceRemoved(device2)
@@ -338,7 +289,7 @@
verify(eventManager).registerCallback(btCallbackCaptor.capture())
`when`(assistant.allConnectedDevices).thenReturn(listOf(device1))
Settings.Secure.putInt(
- context.contentResolver,
+ contentResolver,
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
TEST_GROUP_ID1)
btCallbackCaptor.value.onProfileConnectionStateChanged(cachedDevice2, state, profile)
@@ -352,7 +303,7 @@
contentObserverCaptor.capture())
`when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
Settings.Secure.putInt(
- context.contentResolver,
+ contentResolver,
BluetoothUtils.getPrimaryGroupIdUriForBroadcast(),
TEST_GROUP_ID2)
contentObserverCaptor.value.primaryChanged()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 28bf348..370d568 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -63,22 +63,15 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private CachedBluetoothDevice mCachedBluetoothDevice;
- @Mock
- private BluetoothDevice mBluetoothDevice;
- @Mock
- private AudioManager mAudioManager;
- @Mock
- private PackageManager mPackageManager;
- @Mock
- private LocalBluetoothLeBroadcast mBroadcast;
- @Mock
- private LocalBluetoothProfileManager mProfileManager;
- @Mock
- private LocalBluetoothManager mLocalBluetoothManager;
- @Mock
- private LocalBluetoothLeBroadcastAssistant mAssistant;
- @Mock
- private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
+
+ @Mock private BluetoothDevice mBluetoothDevice;
+ @Mock private AudioManager mAudioManager;
+ @Mock private PackageManager mPackageManager;
+ @Mock private LocalBluetoothLeBroadcast mBroadcast;
+ @Mock private LocalBluetoothProfileManager mProfileManager;
+ @Mock private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock private LocalBluetoothLeBroadcastAssistant mAssistant;
+ @Mock private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
private Context mContext;
private static final String STRING_METADATA = "string_metadata";
@@ -87,13 +80,13 @@
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
private static final String CONTROL_METADATA =
- "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA
+ "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>"
+ + STRING_METADATA
+ "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
@@ -108,20 +101,20 @@
@Test
public void getBtClassDrawableWithDescription_typePhone_returnPhoneDrawable() {
- when(mCachedBluetoothDevice.getBtClass().getMajorDeviceClass()).thenReturn(
- BluetoothClass.Device.Major.PHONE);
- final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
- mContext, mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getBtClass().getMajorDeviceClass())
+ .thenReturn(BluetoothClass.Device.Major.PHONE);
+ final Pair<Drawable, String> pair =
+ BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedBluetoothDevice);
verify(mContext).getDrawable(com.android.internal.R.drawable.ic_phone);
}
@Test
public void getBtClassDrawableWithDescription_typeComputer_returnComputerDrawable() {
- when(mCachedBluetoothDevice.getBtClass().getMajorDeviceClass()).thenReturn(
- BluetoothClass.Device.Major.COMPUTER);
- final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
- mContext, mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getBtClass().getMajorDeviceClass())
+ .thenReturn(BluetoothClass.Device.Major.COMPUTER);
+ final Pair<Drawable, String> pair =
+ BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedBluetoothDevice);
verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_laptop);
}
@@ -136,133 +129,146 @@
@Test
public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("false".getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+ .thenReturn("false".getBytes());
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
when(mCachedBluetoothDevice.getAddress()).thenReturn("1f:aa:bb");
- assertThat(BluetoothUtils.getBtRainbowDrawableWithDescription(
- RuntimeEnvironment.application,
- mCachedBluetoothDevice).first).isInstanceOf(AdaptiveIcon.class);
+ assertThat(
+ BluetoothUtils.getBtRainbowDrawableWithDescription(
+ RuntimeEnvironment.application, mCachedBluetoothDevice)
+ .first)
+ .isInstanceOf(AdaptiveIcon.class);
}
@Test
public void getStringMetaData_hasMetaData_getCorrectMetaData() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON)).thenReturn(
- STRING_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON))
+ .thenReturn(STRING_METADATA.getBytes());
- assertThat(BluetoothUtils.getStringMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON)).isEqualTo(STRING_METADATA);
+ assertThat(
+ BluetoothUtils.getStringMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON))
+ .isEqualTo(STRING_METADATA);
}
@Test
public void getIntMetaData_hasMetaData_getCorrectMetaData() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(
- INT_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY))
+ .thenReturn(INT_METADATA.getBytes());
- assertThat(BluetoothUtils.getIntMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY))
+ assertThat(
+ BluetoothUtils.getIntMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY))
.isEqualTo(Integer.parseInt(INT_METADATA));
}
@Test
public void getIntMetaData_invalidMetaData_getErrorCode() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn(null);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY))
+ .thenReturn(null);
- assertThat(BluetoothUtils.getIntMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON))
+ assertThat(
+ BluetoothUtils.getIntMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_UNTETHERED_LEFT_ICON))
.isEqualTo(BluetoothUtils.META_INT_ERROR);
}
@Test
public void getBooleanMetaData_hasMetaData_getCorrectMetaData() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
- BOOL_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+ .thenReturn(BOOL_METADATA.getBytes());
- assertThat(BluetoothUtils.getBooleanMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).isEqualTo(true);
+ assertThat(
+ BluetoothUtils.getBooleanMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+ .isTrue();
}
@Test
public void getUriMetaData_hasMetaData_getCorrectMetaData() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(
- STRING_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON))
+ .thenReturn(STRING_METADATA.getBytes());
- assertThat(BluetoothUtils.getUriMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_MAIN_ICON)).isEqualTo(Uri.parse(STRING_METADATA));
+ assertThat(
+ BluetoothUtils.getUriMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON))
+ .isEqualTo(Uri.parse(STRING_METADATA));
}
@Test
public void getUriMetaData_nullMetaData_getNullUri() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(null);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(null);
- assertThat(BluetoothUtils.getUriMetaData(mBluetoothDevice,
- BluetoothDevice.METADATA_MAIN_ICON)).isNull();
+ assertThat(
+ BluetoothUtils.getUriMetaData(
+ mBluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON))
+ .isNull();
}
@Test
public void getControlUriMetaData_hasMetaData_returnsCorrectMetaData() {
- when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS)).thenReturn(
- CONTROL_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(CONTROL_METADATA.getBytes());
- assertThat(BluetoothUtils.getControlUriMetaData(mBluetoothDevice)).isEqualTo(
- STRING_METADATA);
+ assertThat(BluetoothUtils.getControlUriMetaData(mBluetoothDevice))
+ .isEqualTo(STRING_METADATA);
+ }
+
+ @Test
+ public void getFastPairCustomizedField_hasMetaData_returnsCorrectMetaData() {
+ when(mBluetoothDevice.getMetadata(METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ .thenReturn(CONTROL_METADATA.getBytes());
+
+ assertThat(
+ BluetoothUtils.getFastPairCustomizedField(
+ mBluetoothDevice, KEY_HEARABLE_CONTROL_SLICE))
+ .isEqualTo(STRING_METADATA);
}
@Test
public void isAdvancedDetailsHeader_untetheredHeadset_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
- BOOL_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+ .thenReturn(BOOL_METADATA.getBytes());
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedDetailsHeader_deviceTypeUntetheredHeadset_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedDetailsHeader_deviceTypeWatch_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedDetailsHeader_deviceTypeStylus_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes());
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedDetailsHeader_deviceTypeDefault_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedDetailsHeader_noMetadata_returnFalse() {
- assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isFalse();
}
@Test
@@ -286,43 +292,39 @@
@Test
public void isAdvancedUntetheredDevice_untetheredHeadset_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
- BOOL_METADATA.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+ .thenReturn(BOOL_METADATA.getBytes());
- assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedUntetheredDevice_deviceTypeUntetheredHeadset_returnTrue() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
- assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isTrue();
}
@Test
public void isAdvancedUntetheredDevice_deviceTypeWatch_returnFalse() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_WATCH.getBytes());
- assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isFalse();
}
@Test
public void isAdvancedUntetheredDevice_deviceTypeDefault_returnFalse() {
- when(mBluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_DEFAULT.getBytes());
- assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isFalse();
}
@Test
public void isAdvancedUntetheredDevice_noMetadata_returnFalse() {
- assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isFalse();
}
@Test
@@ -344,8 +346,10 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(true);
+ assertThat(
+ BluetoothUtils.isAvailableMediaBluetoothDevice(
+ mCachedBluetoothDevice, mAudioManager))
+ .isTrue();
}
@Test
@@ -356,8 +360,10 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(false);
+ assertThat(
+ BluetoothUtils.isAvailableMediaBluetoothDevice(
+ mCachedBluetoothDevice, mAudioManager))
+ .isFalse();
}
@Test
@@ -368,8 +374,10 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(true);
+ assertThat(
+ BluetoothUtils.isAvailableMediaBluetoothDevice(
+ mCachedBluetoothDevice, mAudioManager))
+ .isTrue();
}
@Test
@@ -380,8 +388,10 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(true);
+ assertThat(
+ BluetoothUtils.isAvailableMediaBluetoothDevice(
+ mCachedBluetoothDevice, mAudioManager))
+ .isTrue();
}
@Test
@@ -391,8 +401,8 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(false);
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
+ .isFalse();
}
@Test
@@ -403,8 +413,8 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(true);
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
+ .isTrue();
}
@Test
@@ -415,8 +425,8 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(false);
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
+ .isFalse();
}
@Test
@@ -427,8 +437,8 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(false);
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
+ .isFalse();
}
@Test
@@ -439,58 +449,61 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(false);
- assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
- mAudioManager)).isEqualTo(false);
+ assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice, mAudioManager))
+ .isFalse();
}
@Test
public void isExclusivelyManaged_hasNoManager_returnFalse() {
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- null);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(null);
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isFalse();
}
@Test
public void isExclusivelyManaged_hasPackageName_packageNotInstalled_returnFalse()
throws Exception {
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+ doThrow(new PackageManager.NameNotFoundException())
+ .when(mPackageManager)
.getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isFalse();
}
@Test
public void isExclusivelyManaged_hasComponentName_packageNotInstalled_returnFalse()
throws Exception {
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+ doThrow(new PackageManager.NameNotFoundException())
+ .when(mPackageManager)
.getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isFalse();
}
@Test
public void isExclusivelyManaged_hasPackageName_packageNotEnabled_returnFalse()
- throws Exception {
+ throws Exception {
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.enabled = false;
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doReturn(appInfo).when(mPackageManager).getApplicationInfo(
- TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+ doReturn(appInfo)
+ .when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isFalse();
}
@Test
@@ -499,45 +512,48 @@
ApplicationInfo appInfo = new ApplicationInfo();
appInfo.enabled = false;
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doReturn(appInfo).when(mPackageManager).getApplicationInfo(
- TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+ doReturn(appInfo)
+ .when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(false);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isFalse();
}
@Test
public void isExclusivelyManaged_hasPackageName_packageInstalledAndEnabled_returnTrue()
throws Exception {
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
- TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ doReturn(new ApplicationInfo())
+ .when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isTrue();
}
@Test
public void isExclusivelyManaged_hasComponentName_packageInstalledAndEnabled_returnTrue()
throws Exception {
- when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
- TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo(
- TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
+ doReturn(new ApplicationInfo())
+ .when(mPackageManager)
+ .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0);
- assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
- mBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice))
+ .isTrue();
}
@Test
public void testIsBroadcasting_broadcastEnabled_returnTrue() {
when(mBroadcast.isEnabled(any())).thenReturn(true);
- assertThat(BluetoothUtils.isBroadcasting(mLocalBluetoothManager)).isEqualTo(true);
+ assertThat(BluetoothUtils.isBroadcasting(mLocalBluetoothManager)).isTrue();
}
@Test
@@ -553,9 +569,9 @@
when(mAssistant.getAllSources(any())).thenReturn(sourceList);
assertThat(
- BluetoothUtils.hasConnectedBroadcastSource(
- mCachedBluetoothDevice, mLocalBluetoothManager))
- .isEqualTo(true);
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
}
@Test
@@ -565,7 +581,7 @@
when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mBluetoothDevice.isConnected()).thenReturn(true);
- assertThat(BluetoothUtils.isAvailableHearingDevice(mCachedBluetoothDevice)).isEqualTo(true);
+ assertThat(BluetoothUtils.isAvailableHearingDevice(mCachedBluetoothDevice)).isTrue();
}
@Test
@@ -589,7 +605,8 @@
@Test
public void getSecondaryDeviceForBroadcast_errorState_returnNull() {
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext, mLocalBluetoothManager))
+ assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
+ mLocalBluetoothManager))
.isNull();
}
@@ -608,7 +625,8 @@
when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(ImmutableList.of(state));
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mBluetoothDevice));
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext, mLocalBluetoothManager))
+ assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
+ mLocalBluetoothManager))
.isNull();
}
@@ -637,7 +655,8 @@
when(mAssistant.getAllConnectedDevices())
.thenReturn(ImmutableList.of(mBluetoothDevice, bluetoothDevice));
- assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext, mLocalBluetoothManager))
+ assertThat(BluetoothUtils.getSecondaryDeviceForBroadcast(mContext.getContentResolver(),
+ mLocalBluetoothManager))
.isEqualTo(mCachedBluetoothDevice);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
new file mode 100644
index 0000000..b5457c5
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -0,0 +1,394 @@
+/*
+ * 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.bluetooth.devicesettings.data.repository
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference
+import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreferenceState
+import com.android.settingslib.bluetooth.devicesettings.DeviceInfo
+import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingItem
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingState
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsConfigProviderService
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsProviderService
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class DeviceSettingRepositoryTest {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var cachedDevice: CachedBluetoothDevice
+ @Mock private lateinit var bluetoothDevice: BluetoothDevice
+ @Mock private lateinit var context: Context
+ @Mock private lateinit var bluetoothAdapter: BluetoothAdapter
+ @Mock private lateinit var configService: IDeviceSettingsConfigProviderService.Stub
+ @Mock private lateinit var settingProviderService1: IDeviceSettingsProviderService.Stub
+ @Mock private lateinit var settingProviderService2: IDeviceSettingsProviderService.Stub
+ @Captor
+ private lateinit var metadataChangeCaptor:
+ ArgumentCaptor<BluetoothAdapter.OnMetadataChangedListener>
+
+ private lateinit var underTest: DeviceSettingRepository
+ private val testScope = TestScope()
+
+ @Before
+ fun setUp() {
+ `when`(cachedDevice.device).thenReturn(bluetoothDevice)
+ `when`(cachedDevice.address).thenReturn(BLUETOOTH_ADDRESS)
+ `when`(
+ bluetoothDevice.getMetadata(
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
+ )
+ )
+ .thenReturn(BLUETOOTH_DEVICE_METADATA.toByteArray())
+
+ `when`(configService.queryLocalInterface(anyString())).thenReturn(configService)
+ `when`(settingProviderService1.queryLocalInterface(anyString()))
+ .thenReturn(settingProviderService1)
+ `when`(settingProviderService2.queryLocalInterface(anyString()))
+ .thenReturn(settingProviderService2)
+
+ `when`(context.bindService(any(), any(), anyInt())).then { input ->
+ val intent = input.getArgument<Intent?>(0)
+ val connection = input.getArgument<ServiceConnection>(1)
+
+ when (intent?.action) {
+ CONFIG_SERVICE_INTENT_ACTION ->
+ connection.onServiceConnected(
+ ComponentName(CONFIG_SERVICE_PACKAGE_NAME, CONFIG_SERVICE_CLASS_NAME),
+ configService,
+ )
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_1 ->
+ connection.onServiceConnected(
+ ComponentName(
+ SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1,
+ SETTING_PROVIDER_SERVICE_CLASS_NAME_1
+ ),
+ settingProviderService1,
+ )
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_2 ->
+ connection.onServiceConnected(
+ ComponentName(
+ SETTING_PROVIDER_SERVICE_PACKAGE_NAME_2,
+ SETTING_PROVIDER_SERVICE_CLASS_NAME_2,
+ ),
+ settingProviderService2,
+ )
+ }
+ true
+ }
+ underTest =
+ DeviceSettingRepositoryImpl(
+ context,
+ bluetoothAdapter,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ )
+ }
+
+ @After
+ fun clean() {
+ DeviceSettingServiceConnection.services.clear()
+ }
+
+ @Test
+ fun getDeviceSettingsConfig_withMetadata_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+
+ val config = underTest.getDeviceSettingsConfig(cachedDevice)
+
+ assertThat(config).isSameInstanceAs(DEVICE_SETTING_CONFIG)
+ }
+ }
+
+ @Test
+ fun getDeviceSettingsConfig_waitMetadataChange_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(
+ bluetoothDevice.getMetadata(
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
+ )
+ )
+ .thenReturn("".toByteArray())
+
+ var config: DeviceSettingsConfig? = null
+ val job = launch { config = underTest.getDeviceSettingsConfig(cachedDevice) }
+ delay(1000)
+ verify(bluetoothAdapter)
+ .addOnMetadataChangedListener(
+ eq(bluetoothDevice),
+ any(),
+ metadataChangeCaptor.capture()
+ )
+ metadataChangeCaptor.value.onMetadataChanged(
+ bluetoothDevice,
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
+ BLUETOOTH_DEVICE_METADATA.toByteArray(),
+ )
+ `when`(
+ bluetoothDevice.getMetadata(
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
+ )
+ )
+ .thenReturn(BLUETOOTH_DEVICE_METADATA.toByteArray())
+
+ job.join()
+ assertThat(config).isSameInstanceAs(DEVICE_SETTING_CONFIG)
+ }
+ }
+
+ @Test
+ fun getDeviceSettingsConfig_bindingServiceFail_returnNull() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ doReturn(false).`when`(context).bindService(any(), any(), anyInt())
+
+ val config = underTest.getDeviceSettingsConfig(cachedDevice)
+
+ assertThat(config).isNull()
+ }
+ }
+
+ @Test
+ fun getDeviceSettingList_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
+ }
+ `when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
+ }
+ var settings: List<DeviceSetting>? = null
+
+ underTest
+ .getDeviceSettingList(cachedDevice)
+ .onEach { settings = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ assertThat(settings?.map { it.settingId })
+ .containsExactly(
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ DeviceSettingId.DEVICE_SETTING_ID_ANC
+ )
+ assertThat(settings?.map { (it.preference as ActionSwitchPreference).title })
+ .containsExactly(
+ "title1",
+ "title2",
+ )
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_oneServiceFailed_returnPartialResult() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
+ }
+ var settings: List<DeviceSetting>? = null
+
+ underTest
+ .getDeviceSettingList(cachedDevice)
+ .onEach { settings = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ assertThat(settings?.map { it.settingId })
+ .containsExactly(
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ )
+ assertThat(settings?.map { (it.preference as ActionSwitchPreference).title })
+ .containsExactly(
+ "title1",
+ )
+ }
+ }
+
+ @Test
+ fun getDeviceSetting_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
+ }
+ var setting: DeviceSetting? = null
+
+ underTest
+ .getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+ .onEach { setting = it }
+ .launchIn(backgroundScope)
+ runCurrent()
+
+ assertThat(setting?.settingId).isEqualTo(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+ assertThat((setting?.preference as ActionSwitchPreference).title).isEqualTo("title1")
+ }
+ }
+
+ @Test
+ fun updateDeviceSetting_success() {
+ testScope.runTest {
+ `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ `when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
+ input ->
+ input
+ .getArgument<IDeviceSettingsListener>(1)
+ .onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
+ }
+
+ underTest.updateDeviceSettingState(
+ cachedDevice,
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ ActionSwitchPreferenceState.Builder().build()
+ )
+ runCurrent()
+
+ verify(settingProviderService1)
+ .updateDeviceSettings(
+ DEVICE_INFO,
+ DeviceSettingState.Builder()
+ .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+ .setPreferenceState(ActionSwitchPreferenceState.Builder().build())
+ .build()
+ )
+ }
+ }
+
+ private companion object {
+ const val BLUETOOTH_ADDRESS = "12:34:56:78"
+ const val CONFIG_SERVICE_PACKAGE_NAME = "com.android.fake.configservice"
+ const val CONFIG_SERVICE_CLASS_NAME = "com.android.fake.configservice.Service"
+ const val CONFIG_SERVICE_INTENT_ACTION = "com.android.fake.configservice.BIND"
+ const val SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1 =
+ "com.android.fake.settingproviderservice1"
+ const val SETTING_PROVIDER_SERVICE_CLASS_NAME_1 =
+ "com.android.fake.settingproviderservice1.Service"
+ const val SETTING_PROVIDER_SERVICE_INTENT_ACTION_1 =
+ "com.android.fake.settingproviderservice1.BIND"
+ const val SETTING_PROVIDER_SERVICE_PACKAGE_NAME_2 =
+ "com.android.fake.settingproviderservice2"
+ const val SETTING_PROVIDER_SERVICE_CLASS_NAME_2 =
+ "com.android.fake.settingproviderservice2.Service"
+ const val SETTING_PROVIDER_SERVICE_INTENT_ACTION_2 =
+ "com.android.fake.settingproviderservice2.BIND"
+ const val BLUETOOTH_DEVICE_METADATA =
+ "<DEVICE_SETTINGS_CONFIG_PACKAGE_NAME>" +
+ CONFIG_SERVICE_PACKAGE_NAME +
+ "</DEVICE_SETTINGS_CONFIG_PACKAGE_NAME>" +
+ "<DEVICE_SETTINGS_CONFIG_CLASS>" +
+ CONFIG_SERVICE_CLASS_NAME +
+ "</DEVICE_SETTINGS_CONFIG_CLASS>" +
+ "<DEVICE_SETTINGS_CONFIG_ACTION>" +
+ CONFIG_SERVICE_INTENT_ACTION +
+ "</DEVICE_SETTINGS_CONFIG_ACTION>"
+ val DEVICE_INFO = DeviceInfo.Builder().setBluetoothAddress(BLUETOOTH_ADDRESS).build()
+
+ val DEVICE_SETTING_ITEM_1 =
+ DeviceSettingItem(
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1,
+ SETTING_PROVIDER_SERVICE_CLASS_NAME_1,
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_1
+ )
+ val DEVICE_SETTING_ITEM_2 =
+ DeviceSettingItem(
+ DeviceSettingId.DEVICE_SETTING_ID_ANC,
+ SETTING_PROVIDER_SERVICE_PACKAGE_NAME_2,
+ SETTING_PROVIDER_SERVICE_CLASS_NAME_2,
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_2
+ )
+ val DEVICE_SETTING_1 =
+ DeviceSetting.Builder()
+ .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
+ .setPreference(
+ ActionSwitchPreference.Builder()
+ .setTitle("title1")
+ .setHasSwitch(true)
+ .setAllowedChangingState(true)
+ .build()
+ )
+ .build()
+ val DEVICE_SETTING_2 =
+ DeviceSetting.Builder()
+ .setSettingId(DeviceSettingId.DEVICE_SETTING_ID_ANC)
+ .setPreference(
+ ActionSwitchPreference.Builder()
+ .setTitle("title2")
+ .setHasSwitch(true)
+ .setAllowedChangingState(true)
+ .build()
+ )
+ .build()
+ val DEVICE_SETTING_CONFIG =
+ DeviceSettingsConfig(
+ listOf(DEVICE_SETTING_ITEM_1),
+ listOf(DEVICE_SETTING_ITEM_2),
+ "footer"
+ )
+ }
+}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 04d30ed..0b5187c 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -65,6 +65,7 @@
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
<uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
+ <uses-permission android:name="android.permission.READ_DROPBOX_DATA" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE" />
<uses-permission android:name="android.permission.ACCESS_AMBIENT_LIGHT_STATS" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2f90ccc..cfd8f635 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -258,6 +258,7 @@
"tests/src/**/systemui/statusbar/policy/LocationControllerImplTest.java",
"tests/src/**/systemui/statusbar/policy/RemoteInputViewTest.java",
"tests/src/**/systemui/statusbar/policy/SmartReplyViewTest.java",
+ "tests/src/**/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt",
"tests/src/**/systemui/statusbar/StatusBarStateControllerImplTest.kt",
"tests/src/**/systemui/theme/ThemeOverlayApplierTest.java",
"tests/src/**/systemui/touch/TouchInsetManagerTest.java",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index ba84287..c1e43c9 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -19,3 +19,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "action_bar_wrap_content"
+ namespace: "accessibility"
+ description: "Applies WRAP_CONTENT to the action bar in A11yMenu settings to better fit large fonts"
+ bug: "347911378"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index ab8f97a..c71ef83 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -26,6 +26,7 @@
import android.provider.Browser;
import android.provider.Settings;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.TextView;
import android.window.OnBackInvokedCallback;
@@ -35,6 +36,7 @@
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
+import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
/**
@@ -60,6 +62,18 @@
((TextView) findViewById(R.id.action_bar_title)).setText(
getResources().getString(R.string.accessibility_menu_settings_name)
);
+ if (Flags.actionBarWrapContent()) {
+ setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar));
+ setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar_container));
+ }
+ }
+
+ private void setHeightWrapContent(View view) {
+ if (view != null) {
+ ViewGroup.LayoutParams params = view.getLayoutParams();
+ params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ view.setLayoutParams(params);
+ }
}
@Override
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index eb9d0ab..27f4305 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -601,6 +601,13 @@
}
flag {
+ name: "screenshot_ui_controller_refactor"
+ namespace: "systemui"
+ description: "Simplify and refactor ScreenshotController"
+ bug: "354711957"
+}
+
+flag {
name: "run_fingerprint_detect_on_dismissible_keyguard"
namespace: "systemui"
description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
@@ -1206,6 +1213,16 @@
}
flag {
+ name: "hubmode_fullscreen_vertical_swipe"
+ namespace: "systemui"
+ description: "Enables fullscreen vertical swiping in hub mode to bring up and down the bouncer and shade"
+ bug: "340177049"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
namespace: "systemui"
name: "remove_update_listener_in_qs_icon_view_impl"
description: "Remove update listeners in QsIconViewImpl class to avoid memory leak."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index f73b6cd..9fd30b4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -33,7 +33,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
object Bouncer {
object Elements {
@@ -56,7 +56,7 @@
) : ComposableScene {
override val key = Scenes.Bouncer
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
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 35db9e0..8245cc5 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
@@ -88,12 +88,12 @@
}
to(CommunalScenes.Communal) {
spec = tween(durationMillis = 1000)
- translate(Communal.Elements.Grid, Edge.Right)
+ translate(Communal.Elements.Grid, Edge.End)
timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) }
}
to(CommunalScenes.Blank) {
spec = tween(durationMillis = 1000)
- translate(Communal.Elements.Grid, Edge.Right)
+ translate(Communal.Elements.Grid, Edge.End)
timestampRange(endMillis = 167) {
fade(Communal.Elements.Grid)
fade(Communal.Elements.IndicationArea)
@@ -186,9 +186,7 @@
scene(
CommunalScenes.Blank,
userActions =
- mapOf(
- Swipe(SwipeDirection.Left, fromSource = Edge.Right) to CommunalScenes.Communal
- )
+ mapOf(Swipe(SwipeDirection.Start, fromSource = Edge.End) to CommunalScenes.Communal)
) {
// This scene shows nothing only allowing for transitions to the communal scene.
Box(modifier = Modifier.fillMaxSize())
@@ -197,11 +195,11 @@
val userActions =
if (glanceableHubBackGesture()) {
mapOf(
- Swipe(SwipeDirection.Right) to CommunalScenes.Blank,
+ Swipe(SwipeDirection.End) to CommunalScenes.Blank,
Back to CommunalScenes.Blank,
)
} else {
- mapOf(Swipe(SwipeDirection.Right) to CommunalScenes.Blank)
+ mapOf(Swipe(SwipeDirection.End) to CommunalScenes.Blank)
}
scene(CommunalScenes.Communal, userActions = userActions) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 1ce51af..8c38253 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -98,13 +98,7 @@
bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
)
- val bottomAreaPlaceable =
- bottomAreaMeasurable.measure(
- noMinConstraints.copy(
- maxHeight =
- (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
- )
- )
+ val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)
val communalGridPlaceable =
communalGridMeasurable.measure(
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 650c2d3..e29e0fd 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
@@ -42,6 +42,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -51,6 +52,7 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -67,8 +69,10 @@
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Check
@@ -90,6 +94,7 @@
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
@@ -112,6 +117,11 @@
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.changedToUp
+import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInWindow
@@ -121,6 +131,7 @@
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
@@ -137,7 +148,9 @@
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.times
+import androidx.compose.ui.util.fastAll
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.window.layout.WindowMetricsCalculator
@@ -156,6 +169,8 @@
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp
+import com.android.systemui.communal.util.DensityUtils.Companion.scalingAdjustment
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
@@ -200,21 +215,72 @@
ObserveScrollEffect(gridState, viewModel)
- if (!viewModel.isEditMode) {
+ val context = LocalContext.current
+ val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
+ val screenWidth = windowMetrics.bounds.width()
+ val layoutDirection = LocalLayoutDirection.current
+
+ if (viewModel.isEditMode) {
+ ScrollOnNewWidgetAddedEffect(communalContent, gridState)
+ } else {
ScrollOnUpdatedLiveContentEffect(communalContent, gridState)
}
+ val nestedScrollConnection = remember {
+ object : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ // Begin tracking nested scrolling
+ viewModel.onNestedScrolling()
+ return super.onPreScroll(available, source)
+ }
+ }
+ }
+
Box(
modifier =
modifier
.semantics { testTagsAsResourceId = true }
.testTag(COMMUNAL_HUB_TEST_TAG)
.fillMaxSize()
- .pointerInput(gridState, contentOffset, contentListState) {
+ .nestedScroll(nestedScrollConnection)
+ .pointerInput(layoutDirection, gridState, contentOffset, contentListState) {
+ awaitPointerEventScope {
+ while (true) {
+ var event = awaitFirstDown(requireUnconsumed = false)
+ // Reset touch on first event.
+ viewModel.onResetTouchState()
+
+ // Process down event in case it's consumed immediately
+ if (event.isConsumed) {
+ viewModel.onHubTouchConsumed()
+ }
+
+ do {
+ var event = awaitPointerEvent()
+ for (change in event.changes) {
+ if (change.isConsumed) {
+ // Signal touch consumption on any consumed event.
+ viewModel.onHubTouchConsumed()
+ }
+ }
+ } while (
+ !event.changes.fastAll {
+ it.changedToUp() || it.changedToUpIgnoreConsumed()
+ }
+ )
+ }
+ }
+
// If not in edit mode, don't allow selecting items.
if (!viewModel.isEditMode) return@pointerInput
observeTaps { offset ->
- val adjustedOffset = offset - contentOffset
+ // if RTL, flip offset direction from Left side to Right
+ val adjustedOffset =
+ Offset(
+ if (layoutDirection == LayoutDirection.Rtl) screenWidth - offset.x
+ else offset.x,
+ offset.y
+ ) - contentOffset
val index = firstIndexAtOffset(gridState, adjustedOffset)
val key = index?.let { keyAtIndexIfEditable(contentListState.list, index) }
viewModel.setSelectedKey(key)
@@ -232,7 +298,12 @@
// offset.
val adjustedOffset =
gridCoordinates?.let {
- offset - it.positionInWindow() - contentOffset
+ Offset(
+ if (layoutDirection == LayoutDirection.Rtl)
+ screenWidth - offset.x
+ else offset.x,
+ offset.y
+ ) - it.positionInWindow() - contentOffset
}
val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
@@ -283,6 +354,7 @@
viewModel = viewModel,
contentPadding = contentPadding,
contentOffset = contentOffset,
+ screenWidth = screenWidth,
setGridCoordinates = { gridCoordinates = it },
updateDragPositionForRemove = { offset ->
isPointerWithinEnabledRemoveButton(
@@ -481,6 +553,36 @@
}
}
+/** Observes communal content and scrolls to a newly added widget if any. */
+@Composable
+private fun ScrollOnNewWidgetAddedEffect(
+ communalContent: List<CommunalContentModel>,
+ gridState: LazyGridState,
+) {
+ val coroutineScope = rememberCoroutineScope()
+ val widgetKeys = remember { mutableListOf<String>() }
+
+ LaunchedEffect(communalContent) {
+ val oldWidgetKeys = widgetKeys.toList()
+ widgetKeys.clear()
+ widgetKeys.addAll(communalContent.filter { it.isWidgetContent() }.map { it.key })
+
+ // Do nothing if there is no new widget
+ val indexOfFirstNewWidget = widgetKeys.indexOfFirst { !oldWidgetKeys.contains(it) }
+ if (indexOfFirstNewWidget < 0) {
+ return@LaunchedEffect
+ }
+
+ // Scroll if the new widget is not visible
+ val lastVisibleItemIndex = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index
+ if (lastVisibleItemIndex != null && indexOfFirstNewWidget > lastVisibleItemIndex) {
+ // Launching with a scope to prevent the job from being canceled in the case of a
+ // recomposition during scrolling
+ coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstNewWidget) }
+ }
+ }
+}
+
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun BoxScope.CommunalHubLazyGrid(
@@ -488,6 +590,7 @@
viewModel: BaseCommunalViewModel,
contentPadding: PaddingValues,
selectedKey: State<String?>,
+ screenWidth: Int,
contentOffset: Offset,
gridState: LazyGridState,
contentListState: ContentListState,
@@ -510,7 +613,15 @@
updateDragPositionForRemove = updateDragPositionForRemove
)
gridModifier =
- gridModifier.fillMaxSize().dragContainer(dragDropState, contentOffset, viewModel)
+ gridModifier
+ .fillMaxSize()
+ .dragContainer(
+ dragDropState,
+ LocalLayoutDirection.current,
+ screenWidth,
+ contentOffset,
+ viewModel
+ )
// for widgets dropped from other activities
val dragAndDropTargetState =
rememberDragAndDropTargetState(
@@ -604,11 +715,11 @@
Card(
modifier = Modifier.height(hubDimensions.GridHeight).padding(contentPadding),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
- border = BorderStroke(3.dp, colors.secondary),
- shape = RoundedCornerShape(size = 80.dp)
+ border = BorderStroke(3.adjustedDp, colors.secondary),
+ shape = RoundedCornerShape(size = 80.adjustedDp)
) {
Column(
- modifier = Modifier.fillMaxSize().padding(horizontal = 110.dp),
+ modifier = Modifier.fillMaxSize().padding(horizontal = 110.adjustedDp),
verticalArrangement =
Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -791,7 +902,7 @@
onClick = onClick,
colors =
ButtonDefaults.outlinedButtonColors(
- contentColor = colors.primary,
+ contentColor = colors.onPrimaryContainer,
),
border = BorderStroke(width = 2.0.dp, color = colors.primary),
contentPadding = Dimensions.ButtonPadding,
@@ -855,22 +966,22 @@
/** Creates an empty card used to highlight a particular spot on the grid. */
@Composable
fun HighlightedItem(modifier: Modifier = Modifier, alpha: Float = 1.0f) {
- val brush = SolidColor(LocalAndroidColorScheme.current.primaryFixed)
+ val brush = SolidColor(LocalAndroidColorScheme.current.primary)
Box(
modifier =
// drawBehind lets us draw outside the bounds of the widgets so that we don't need to
// resize grid items to account for the border.
modifier.drawBehind {
// 8dp of padding between the widget and the highlight on every side.
- val padding = 8.dp.toPx()
+ val padding = 8.adjustedDp.toPx()
drawRoundRect(
brush,
alpha = alpha,
topLeft = Offset(-padding, -padding),
size =
Size(width = size.width + padding * 2, height = size.height + padding * 2),
- cornerRadius = CornerRadius(37.dp.toPx()),
- style = Stroke(width = 3.dp.toPx())
+ cornerRadius = CornerRadius(37.adjustedDp.toPx()),
+ style = Stroke(width = 3.adjustedDp.toPx())
)
}
)
@@ -890,10 +1001,12 @@
containerColor = colors.primary,
contentColor = colors.onPrimary,
),
- shape = RoundedCornerShape(68.dp, 34.dp, 68.dp, 34.dp)
+ shape = RoundedCornerShape(68.adjustedDp, 34.adjustedDp, 68.adjustedDp, 34.adjustedDp)
) {
Column(
- modifier = Modifier.fillMaxSize().padding(vertical = 32.dp, horizontal = 50.dp),
+ modifier =
+ Modifier.fillMaxSize()
+ .padding(vertical = 32.adjustedDp, horizontal = 50.adjustedDp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
@@ -902,47 +1015,57 @@
contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
modifier = Modifier.size(Dimensions.IconSize).clearAndSetSemantics {},
)
- Spacer(modifier = Modifier.size(6.dp))
+ Spacer(modifier = Modifier.size(6.adjustedDp))
Text(
text = stringResource(R.string.cta_label_to_edit_widget),
style = MaterialTheme.typography.titleLarge,
fontSize = nonScalableTextSize(22.dp),
lineHeight = nonScalableTextSize(28.dp),
+ modifier = Modifier.verticalScroll(rememberScrollState()).weight(1F)
)
- Spacer(modifier = Modifier.size(16.dp))
+ Spacer(modifier = Modifier.size(16.adjustedDp))
Row(
- modifier = Modifier.fillMaxWidth().height(56.dp),
- horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally),
+ modifier = Modifier.fillMaxWidth().height(56.adjustedDp),
+ horizontalArrangement =
+ Arrangement.spacedBy(16.adjustedDp, Alignment.CenterHorizontally),
) {
- OutlinedButton(
- modifier = Modifier.fillMaxHeight(),
- colors =
- ButtonDefaults.buttonColors(
- contentColor = colors.onPrimary,
- ),
- border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
- contentPadding = PaddingValues(26.dp, 8.dp),
- onClick = viewModel::onDismissCtaTile,
+ CompositionLocalProvider(
+ LocalDensity provides
+ Density(
+ LocalDensity.current.density,
+ LocalDensity.current.fontScale.coerceIn(0f, 1.25f)
+ )
) {
- Text(
- text = stringResource(R.string.cta_tile_button_to_dismiss),
- fontSize = nonScalableTextSize(14.dp),
- )
- }
- Button(
- modifier = Modifier.fillMaxHeight(),
- colors =
- ButtonDefaults.buttonColors(
- containerColor = colors.primaryContainer,
- contentColor = colors.onPrimaryContainer,
- ),
- contentPadding = PaddingValues(26.dp, 8.dp),
- onClick = viewModel::onOpenWidgetEditor
- ) {
- Text(
- text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
- fontSize = nonScalableTextSize(14.dp),
- )
+ OutlinedButton(
+ modifier = Modifier.fillMaxHeight().weight(1F),
+ colors =
+ ButtonDefaults.buttonColors(
+ contentColor = colors.onPrimary,
+ ),
+ border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
+ onClick = viewModel::onDismissCtaTile,
+ contentPadding = PaddingValues(0.dp, 0.dp, 0.dp, 0.dp),
+ ) {
+ Text(
+ text = stringResource(R.string.cta_tile_button_to_dismiss),
+ fontSize = 14.sp,
+ )
+ }
+ Button(
+ modifier = Modifier.fillMaxHeight().weight(1F),
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = colors.primaryContainer,
+ contentColor = colors.onPrimaryContainer,
+ ),
+ onClick = viewModel::onOpenWidgetEditor,
+ contentPadding = PaddingValues(0.dp, 0.dp, 0.dp, 0.dp),
+ ) {
+ Text(
+ text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
+ fontSize = 14.sp,
+ )
+ }
}
}
}
@@ -1107,11 +1230,11 @@
visible = visible,
enter = fadeIn(),
exit = fadeOut(),
- modifier = modifier.padding(16.dp),
+ modifier = modifier.padding(16.adjustedDp),
) {
FilledIconButton(
- shape = RoundedCornerShape(16.dp),
- modifier = Modifier.size(48.dp),
+ shape = RoundedCornerShape(16.adjustedDp),
+ modifier = Modifier.size(48.adjustedDp),
colors =
IconButtonColors(
containerColor = colors.primary,
@@ -1124,7 +1247,7 @@
Icon(
imageVector = Icons.Outlined.Edit,
contentDescription = stringResource(id = R.string.edit_widget),
- modifier = Modifier.padding(12.dp)
+ modifier = Modifier.padding(12.adjustedDp)
)
}
}
@@ -1187,7 +1310,9 @@
modifier =
modifier.background(
MaterialTheme.colorScheme.surfaceVariant,
- RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+ RoundedCornerShape(
+ dimensionResource(system_app_widget_background_radius) * scalingAdjustment
+ )
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
@@ -1322,7 +1447,7 @@
private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingInPx {
return with(LocalDensity.current) {
ContentPaddingInPx(
- start = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(),
+ start = paddingValues.calculateStartPadding(LocalLayoutDirection.current).toPx(),
top = paddingValues.calculateTopPadding().toPx()
)
}
@@ -1368,11 +1493,11 @@
val GridTopSpacing: Dp
get() {
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- return 114.dp
+ return 114.adjustedDp
} else {
val windowMetrics =
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
- val screenHeight = with(density) { windowMetrics.bounds.height().toDp() }
+ val screenHeight = with(density) { windowMetrics.bounds.height().adjustedDp }
return (screenHeight - CardHeightFull) / 2
}
@@ -1381,26 +1506,47 @@
val GridHeight = CardHeightFull + GridTopSpacing
companion object {
- val CardHeightFull = 530.dp
- val ItemSpacing = 50.dp
- val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2
- val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3
- val CardWidth = 360.dp
- val CardOutlineWidth = 3.dp
- val Spacing = ItemSpacing / 2
+ val CardHeightFull
+ get() = 530.adjustedDp
+
+ val ItemSpacing
+ get() = 50.adjustedDp
+
+ val CardHeightHalf
+ get() = (CardHeightFull - ItemSpacing) / 2
+
+ val CardHeightThird
+ get() = (CardHeightFull - (2 * ItemSpacing)) / 3
+
+ val CardWidth
+ get() = 360.adjustedDp
+
+ val CardOutlineWidth
+ get() = 3.adjustedDp
+
+ val Spacing
+ get() = ItemSpacing / 2
// The sizing/padding of the toolbar in glanceable hub edit mode
- val ToolbarPaddingTop = 27.dp
- val ToolbarPaddingHorizontal = ItemSpacing
- val ToolbarButtonPaddingHorizontal = 24.dp
- val ToolbarButtonPaddingVertical = 16.dp
+ val ToolbarPaddingTop
+ get() = 27.adjustedDp
+
+ val ToolbarPaddingHorizontal
+ get() = ItemSpacing
+
+ val ToolbarButtonPaddingHorizontal
+ get() = 24.adjustedDp
+
+ val ToolbarButtonPaddingVertical
+ get() = 16.adjustedDp
+
val ButtonPadding =
PaddingValues(
vertical = ToolbarButtonPaddingVertical,
horizontal = ToolbarButtonPaddingHorizontal,
)
- val IconSize = 40.dp
- val SlideOffsetY = 30.dp
+ val IconSize = 40.adjustedDp
+ val SlideOffsetY = 30.adjustedDp
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index 94018bb..5886d7d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -30,8 +30,8 @@
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
/** The communal scene shows glanceable hub when the device is locked and docked. */
@@ -45,10 +45,10 @@
) : ComposableScene {
override val key = Scenes.Communal
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
MutableStateFlow<Map<UserAction, UserActionResult>>(
mapOf(
- Swipe(SwipeDirection.Right) to UserActionResult(Scenes.Lockscreen),
+ Swipe(SwipeDirection.End) to UserActionResult(Scenes.Lockscreen),
)
)
.asStateFlow()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 07898b0..20ee131 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -37,7 +37,9 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
@@ -47,6 +49,9 @@
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
+private fun Float.directional(origin: LayoutDirection, current: LayoutDirection): Float =
+ if (origin == current) this else -this
+
@Composable
fun rememberGridDragDropState(
gridState: LazyGridState,
@@ -113,14 +118,24 @@
*
* @return {@code True} if dragging a grid item, {@code False} otherwise.
*/
- internal fun onDragStart(offset: Offset, contentOffset: Offset): Boolean {
+ internal fun onDragStart(
+ offset: Offset,
+ screenWidth: Int,
+ layoutDirection: LayoutDirection,
+ contentOffset: Offset
+ ): Boolean {
+ val normalizedOffset =
+ Offset(
+ if (layoutDirection == LayoutDirection.Ltr) offset.x else screenWidth - offset.x,
+ offset.y
+ )
state.layoutInfo.visibleItemsInfo
.filter { item -> contentListState.isItemEditable(item.index) }
// grid item offset is based off grid content container so we need to deduct
// before content padding from the initial pointer position
- .firstItemAtOffset(offset - contentOffset)
+ .firstItemAtOffset(normalizedOffset - contentOffset)
?.apply {
- dragStartPointerOffset = offset - this.offset.toOffset()
+ dragStartPointerOffset = normalizedOffset - this.offset.toOffset()
draggingItemIndex = index
draggingItemInitialOffset = this.offset.toOffset()
return true
@@ -145,8 +160,10 @@
dragStartPointerOffset = Offset.Zero
}
- internal fun onDrag(offset: Offset) {
- draggingItemDraggedDelta += offset
+ internal fun onDrag(offset: Offset, layoutDirection: LayoutDirection) {
+ // Adjust offset to match the layout direction
+ draggingItemDraggedDelta +=
+ Offset(offset.x.directional(LayoutDirection.Ltr, layoutDirection), offset.y)
val draggingItem = draggingItemLayoutInfo ?: return
val startOffset = draggingItem.offset.toOffset() + draggingItemOffset
@@ -213,6 +230,8 @@
fun Modifier.dragContainer(
dragDropState: GridDragDropState,
+ layoutDirection: LayoutDirection,
+ screenWidth: Int,
contentOffset: Offset,
viewModel: BaseCommunalViewModel,
): Modifier {
@@ -221,10 +240,17 @@
detectDragGesturesAfterLongPress(
onDrag = { change, offset ->
change.consume()
- dragDropState.onDrag(offset = offset)
+ dragDropState.onDrag(offset, layoutDirection)
},
onDragStart = { offset ->
- if (dragDropState.onDragStart(offset, contentOffset)) {
+ if (
+ dragDropState.onDragStart(
+ offset,
+ screenWidth,
+ layoutDirection,
+ contentOffset
+ )
+ ) {
viewModel.onReorderWidgetStart()
}
},
@@ -262,10 +288,12 @@
targetValue = if (dragDropState.isDraggingToRemove) 0.5f else 1f,
label = "DraggableItemAlpha"
)
+ val direction = LocalLayoutDirection.current
val draggingModifier =
if (dragging) {
Modifier.graphicsLayer {
- translationX = dragDropState.draggingItemOffset.x
+ translationX =
+ dragDropState.draggingItemOffset.x.directional(LayoutDirection.Ltr, direction)
translationY = dragDropState.draggingItemOffset.y
alpha = itemAlpha
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index c241f9c..b077e18 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -30,7 +30,7 @@
import com.android.systemui.scene.ui.composable.ComposableScene
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
/** The lock screen scene shows when the device is locked. */
@SysUISingleton
@@ -42,7 +42,7 @@
) : ComposableScene {
override val key = Scenes.Lockscreen
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
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 c01039d..a9e63c6 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
@@ -31,6 +31,7 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.padding
+import com.android.compose.modifiers.thenIf
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
@@ -94,9 +95,12 @@
with(topAreaSection) {
DefaultClockLayout(
modifier =
- Modifier.fillMaxWidth(0.5f).graphicsLayer {
- translationX = unfoldTranslations.start
- }
+ Modifier.thenIf(isShadeLayoutWide) {
+ Modifier.fillMaxWidth(0.5f)
+ }
+ .graphicsLayer {
+ translationX = unfoldTranslations.start
+ }
)
}
if (isShadeLayoutWide) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index 86639fa..6feaf6d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -32,6 +32,7 @@
import androidx.core.content.res.ResourcesCompat
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -55,6 +56,7 @@
private val vibratorHelper: VibratorHelper,
private val indicationController: KeyguardIndicationController,
private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
+ private val shortcutsLogger: KeyguardQuickAffordancesLogger,
) {
/**
* Renders a single lockscreen shortcut.
@@ -98,7 +100,7 @@
) {
MovableElement(
key = IndicationAreaElementKey,
- modifier = modifier.shortcutPadding(),
+ modifier = modifier.indicationAreaPadding(),
) {
content {
IndicationArea(
@@ -162,6 +164,7 @@
transitionAlpha,
falsingManager,
vibratorHelper,
+ shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
@@ -210,6 +213,11 @@
)
.padding(bottom = dimensionResource(R.dimen.keyguard_affordance_vertical_offset))
}
+
+ @Composable
+ private fun Modifier.indicationAreaPadding(): Modifier {
+ return this.padding(bottom = dimensionResource(R.dimen.keyguard_indication_margin_bottom))
+ }
}
private val StartButtonElementKey = ElementKey("StartButton")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
index 0bd0809..7b497e8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaScenePicker.kt
@@ -19,6 +19,7 @@
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementScenePicker
import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.animation.scene.TransitionState
import com.android.systemui.scene.shared.model.Scenes
@@ -45,22 +46,28 @@
shouldElevateMedia(transition) -> {
Scenes.Shade
}
-
- // TODO: 345467290 - update with the actual scene picking
+ transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Communal) -> {
+ Scenes.Lockscreen
+ }
transition.isTransitioningBetween(Scenes.QuickSettings, Scenes.Shade) -> {
Scenes.QuickSettings
}
-
- // TODO: 340216785 - update with the actual scene picking
- else -> pickSingleSceneIn(scenes, transition, element)
+ else -> {
+ when {
+ scenes.contains(transition.toScene) -> transition.toScene
+ scenes.contains(transition.fromScene) -> transition.fromScene
+ else -> null
+ }
+ }
}
}
/** Returns true when the media should be laid on top of the rest for the given [transition]. */
- fun shouldElevateMedia(transition: TransitionState.Transition?): Boolean {
- if (transition == null) {
- return false
- }
+ fun shouldElevateMedia(transition: TransitionState.Transition): Boolean {
return transition.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)
}
}
+
+fun MediaScenePicker.shouldElevateMedia(layoutState: SceneTransitionLayoutState): Boolean {
+ return layoutState.currentTransition?.let { shouldElevateMedia(it) } ?: false
+}
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 c4970c5..76a7a10 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
@@ -290,6 +290,7 @@
val isCurrentGestureOverscroll =
viewModel.isCurrentGestureOverscroll.collectAsStateWithLifecycle(false)
val expansionFraction by viewModel.expandFraction.collectAsStateWithLifecycle(0f)
+ val shadeToQsFraction by viewModel.shadeToQsFraction.collectAsStateWithLifecycle(0f)
val topPadding = dimensionResource(id = R.dimen.notification_side_paddings)
val navBarHeight = WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
@@ -385,14 +386,26 @@
modifier
.element(Notifications.Elements.NotificationScrim)
.offset {
- // if scrim is expanded while transitioning to Gone scene, increase the offset
- // in step with the transition so that it is 0 when it completes.
+ // if scrim is expanded while transitioning to Gone or QS scene, increase the
+ // offset in step with the corresponding transition so that it is 0 when it
+ // completes.
if (
scrimOffset.value < 0 &&
layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Gone) ||
layoutState.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
) {
IntOffset(x = 0, y = (scrimOffset.value * expansionFraction).roundToInt())
+ } else if (
+ scrimOffset.value < 0 &&
+ layoutState.isTransitioning(
+ from = Scenes.Shade,
+ to = Scenes.QuickSettings
+ )
+ ) {
+ IntOffset(
+ x = 0,
+ y = (scrimOffset.value * (1 - shadeToQsFraction)).roundToInt()
+ )
} else {
IntOffset(x = 0, y = scrimOffset.value.roundToInt())
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index db98bc8f..7159def 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -44,7 +44,7 @@
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
@SysUISingleton
class NotificationsShadeScene
@@ -64,7 +64,7 @@
override val key = Scenes.NotificationsShade
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
sceneViewModel.destinationScenes
@Composable
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 3cf8e70..2800eee 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
@@ -69,6 +69,8 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneDpAsState
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.modifiers.thenIf
@@ -79,7 +81,6 @@
import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -109,16 +110,13 @@
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.Flow
/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
@SysUISingleton
class QuickSettingsScene
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
private val shadeSession: SaveableSession,
private val notificationStackScrollView: Lazy<NotificationScrollView>,
private val viewModel: QuickSettingsSceneViewModel,
@@ -131,12 +129,8 @@
) : ComposableScene {
override val key = Scenes.QuickSettings
- override val destinationScenes =
- viewModel.destinationScenes.stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = emptyMap(),
- )
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index 422c9f6..f6d1283 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -60,7 +60,7 @@
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
@SysUISingleton
class QuickSettingsShadeScene
@@ -76,7 +76,7 @@
override val key = Scenes.QuickSettingsShade
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index a44041a..a9ddf84 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -36,7 +36,7 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import dagger.Lazy
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
/**
* "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
@@ -52,7 +52,7 @@
) : ComposableScene {
override val key = Scenes.Gone
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
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 9c9e6c6..d5874d1 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
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalComposeUiApi::class)
-
package com.android.systemui.scene.ui.composable
import androidx.compose.foundation.layout.Box
@@ -23,14 +21,13 @@
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-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
@@ -40,6 +37,7 @@
import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import kotlinx.coroutines.flow.collectLatest
/**
* Renders a container of a collection of "scenes" that the user can switch between using certain
@@ -62,19 +60,20 @@
fun SceneContainer(
viewModel: SceneContainerViewModel,
sceneByKey: Map<SceneKey, ComposableScene>,
+ initialSceneKey: SceneKey,
dataSourceDelegator: SceneDataSourceDelegator,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
- val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle()
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
- initialScene = currentSceneKey,
+ initialScene = initialSceneKey,
canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
transitions = SceneContainerTransitions,
enableInterruptions = false,
)
}
+ val currentSceneKey = state.transitionState.currentScene
DisposableEffect(state) {
val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
@@ -87,19 +86,32 @@
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
+ val userActionsBySceneKey: MutableMap<SceneKey, Map<UserAction, UserActionResult>> = remember {
+ mutableStateMapOf()
+ }
+ LaunchedEffect(currentSceneKey) {
+ try {
+ sceneByKey[currentSceneKey]?.destinationScenes?.collectLatest { userActions ->
+ userActionsBySceneKey[currentSceneKey] = viewModel.resolveSceneFamilies(userActions)
+ }
+ } finally {
+ userActionsBySceneKey[currentSceneKey] = emptyMap()
}
+ }
Box(
modifier = Modifier.fillMaxSize(),
) {
SceneTransitionLayout(state = state, modifier = modifier.fillMaxSize()) {
sceneByKey.forEach { (sceneKey, composableScene) ->
- scene(key = sceneKey, userActions = checkNotNull(userActionsBySceneKey[sceneKey])) {
+ scene(
+ key = sceneKey,
+ userActions = userActionsBySceneKey.getOrDefault(sceneKey, emptyMap())
+ ) {
+ // Activate the scene.
+ LaunchedEffect(composableScene) { composableScene.activate() }
+
+ // Render the scene.
with(composableScene) {
[email protected](
modifier = Modifier.element(sceneKey.rootElementKey).fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
index f14ff76..2f8c248 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt
@@ -16,10 +16,58 @@
package com.android.systemui.scene.ui.composable.transitions
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.controls.ui.composable.MediaScenePicker
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
+import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.goneToSplitShadeTransition(
durationScale: Double = 1.0,
) {
- toSplitShadeTransition(durationScale)
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ swipeSpec =
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
+ )
+ distance =
+ object : UserActionDistance {
+ override fun UserActionDistanceScope.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return fromSceneSize.height.toFloat() * 2 / 3f
+ }
+ }
+
+ fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
+
+ fractionRange(start = .33f) {
+ val qsTranslation = ShadeHeader.Dimensions.CollapsedHeight * MediaScenePicker.SHADE_FRACTION
+ val qsExpansionDiff =
+ ShadeHeader.Dimensions.ExpandedHeight - ShadeHeader.Dimensions.CollapsedHeight
+ translate(MediaCarousel.Elements.Content, y = -(qsExpansionDiff + qsTranslation))
+ fade(MediaCarousel.Elements.Content)
+
+ fade(ShadeHeader.Elements.Clock)
+ fade(ShadeHeader.Elements.CollapsedContentStart)
+ fade(ShadeHeader.Elements.CollapsedContentEnd)
+ fade(ShadeHeader.Elements.PrivacyChip)
+ fade(QuickSettings.Elements.SplitShadeQuickSettings)
+ fade(QuickSettings.Elements.FooterActions)
+ fade(Notifications.Elements.NotificationScrim)
+ }
}
+
+private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
index 0021bf5..5401936 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToCommunalTransition.kt
@@ -25,8 +25,8 @@
spec = tween(durationMillis = 500)
// Translate lockscreen to the left.
- translate(Scenes.Lockscreen.rootElementKey, Edge.Left)
+ translate(Scenes.Lockscreen.rootElementKey, Edge.Start)
// Translate communal from the right.
- translate(Scenes.Communal.rootElementKey, Edge.Right)
+ translate(Scenes.Communal.rootElementKey, Edge.End)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
index 70c343c..1486ea7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt
@@ -16,10 +16,50 @@
package com.android.systemui.scene.ui.composable.transitions
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
+import com.android.compose.animation.scene.UserActionDistanceScope
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
+import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.lockscreenToSplitShadeTransition(
durationScale: Double = 1.0,
) {
- toSplitShadeTransition(durationScale = durationScale)
+ spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
+ swipeSpec =
+ spring(
+ stiffness = Spring.StiffnessMediumLow,
+ visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
+ )
+ distance =
+ object : UserActionDistance {
+ override fun UserActionDistanceScope.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return fromSceneSize.height.toFloat() * 2 / 3f
+ }
+ }
+
+ fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
+
+ fractionRange(start = .33f) {
+ fade(ShadeHeader.Elements.Clock)
+ fade(ShadeHeader.Elements.CollapsedContentStart)
+ fade(ShadeHeader.Elements.CollapsedContentEnd)
+ fade(ShadeHeader.Elements.PrivacyChip)
+ fade(QuickSettings.Elements.SplitShadeQuickSettings)
+ fade(QuickSettings.Elements.FooterActions)
+ fade(Notifications.Elements.NotificationScrim)
+ }
}
+
+private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt
deleted file mode 100644
index a8315c0..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt
+++ /dev/null
@@ -1,65 +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.scene.ui.composable.transitions
-
-import androidx.compose.animation.core.Spring
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.ui.unit.IntSize
-import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
-import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.qs.ui.composable.QuickSettings
-import com.android.systemui.shade.ui.composable.Shade
-import com.android.systemui.shade.ui.composable.ShadeHeader
-import kotlin.time.Duration.Companion.milliseconds
-
-fun TransitionBuilder.toSplitShadeTransition(
- durationScale: Double = 1.0,
-) {
- spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- swipeSpec =
- spring(
- stiffness = Spring.StiffnessMediumLow,
- visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold,
- )
- distance =
- object : UserActionDistance {
- override fun UserActionDistanceScope.absoluteDistance(
- fromSceneSize: IntSize,
- orientation: Orientation,
- ): Float {
- return fromSceneSize.height.toFloat() * 2 / 3f
- }
- }
-
- fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) }
-
- fractionRange(start = .33f) {
- fade(ShadeHeader.Elements.Clock)
- fade(ShadeHeader.Elements.CollapsedContentStart)
- fade(ShadeHeader.Elements.CollapsedContentEnd)
- fade(ShadeHeader.Elements.PrivacyChip)
- fade(QuickSettings.Elements.SplitShadeQuickSettings)
- fade(QuickSettings.Elements.FooterActions)
- fade(Notifications.Elements.NotificationScrim)
- }
-}
-
-private val DefaultDuration = 500.milliseconds
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 09414a9..18ca0f7 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
@@ -60,6 +60,7 @@
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
@@ -81,10 +82,12 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.composable.MediaScenePicker
+import com.android.systemui.media.controls.ui.composable.shouldElevateMedia
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.dagger.MediaModule.QS_PANEL
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
import com.android.systemui.notifications.ui.composable.NotificationStackCutoffGuideline
@@ -109,7 +112,7 @@
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
object Shade {
object Elements {
@@ -148,12 +151,17 @@
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
private val statusBarIconController: StatusBarIconController,
private val mediaCarouselController: MediaCarouselController,
- @Named(QUICK_QS_PANEL) private val mediaHost: MediaHost,
+ @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
+ @Named(QS_PANEL) private val qsMediaHost: MediaHost,
) : ComposableScene {
override val key = Scenes.Shade
- override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ override suspend fun activate() {
+ viewModel.activate()
+ }
+
+ override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
viewModel.destinationScenes
@Composable
@@ -168,15 +176,20 @@
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
statusBarIconController = statusBarIconController,
mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
+ qqsMediaHost = qqsMediaHost,
+ qsMediaHost = qsMediaHost,
modifier = modifier,
shadeSession = shadeSession,
)
init {
- mediaHost.expansion = MediaHostState.EXPANDED
- mediaHost.showsOnlyActiveMedia = true
- mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+ qqsMediaHost.expansion = MediaHostState.EXPANDED
+ qqsMediaHost.showsOnlyActiveMedia = true
+ qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+
+ qsMediaHost.expansion = MediaHostState.EXPANDED
+ qsMediaHost.showsOnlyActiveMedia = false
+ qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
}
}
@@ -189,7 +202,8 @@
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
statusBarIconController: StatusBarIconController,
mediaCarouselController: MediaCarouselController,
- mediaHost: MediaHost,
+ qqsMediaHost: MediaHost,
+ qsMediaHost: MediaHost,
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
) {
@@ -204,7 +218,7 @@
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
+ mediaHost = qqsMediaHost,
modifier = modifier,
shadeSession = shadeSession,
)
@@ -217,7 +231,7 @@
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
+ mediaHost = qsMediaHost,
modifier = modifier,
shadeSession = shadeSession,
)
@@ -362,7 +376,7 @@
layout(constraints.maxWidth, constraints.maxHeight) {
val qsZIndex =
- if (MediaScenePicker.shouldElevateMedia(layoutState.currentTransition)) {
+ if (MediaScenePicker.shouldElevateMedia(layoutState)) {
1f
} else {
0f
@@ -471,17 +485,20 @@
val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
- Box(
- modifier =
- modifier
- .fillMaxSize()
- .element(Shade.Elements.BackgroundScrim)
- // Cannot set the alpha of the whole element to 0, because the mirror should be
- // in the QS column.
- .background(
- colorResource(R.color.shade_scrim_background_dark).copy(alpha = contentAlpha)
- )
- ) {
+ Box {
+ Box(
+ modifier =
+ modifier
+ .fillMaxSize()
+ .element(Shade.Elements.BackgroundScrim)
+ // Cannot set the alpha of the whole element to 0, because the mirror should be
+ // in the QS column.
+ .background(
+ colorResource(R.color.shade_scrim_background_dark)
+ .copy(alpha = contentAlpha)
+ )
+ )
+
Column(
modifier = Modifier.fillMaxSize(),
) {
@@ -541,11 +558,15 @@
squishiness = { tileSquishiness },
)
}
-
MediaCarousel(
isVisible = isMediaVisible,
mediaHost = mediaHost,
- modifier = Modifier.fillMaxWidth(),
+ modifier =
+ Modifier.fillMaxWidth().thenIf(
+ MediaScenePicker.shouldElevateMedia(layoutState)
+ ) {
+ Modifier.zIndex(1f)
+ },
carouselController = mediaCarouselController,
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 324e7bd..b329534 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -16,12 +16,16 @@
package com.android.compose.animation.scene
+import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
@@ -36,13 +40,11 @@
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.input.pointer.util.addPointerInputChange
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
-import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.PointerInputModifierNode
import androidx.compose.ui.node.currentValueOf
-import androidx.compose.ui.node.findNearestAncestor
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.IntSize
@@ -51,6 +53,7 @@
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastFirstOrNull
import androidx.compose.ui.util.fastSumBy
+import com.android.compose.ui.util.SpaceVectorConverter
import kotlin.coroutines.cancellation.CancellationException
import kotlin.math.sign
import kotlinx.coroutines.coroutineScope
@@ -71,6 +74,7 @@
* dragged) and a second pointer is down and dragged. This is an implementation detail that might
* change in the future.
*/
+@VisibleForTesting
@Stable
internal fun Modifier.multiPointerDraggable(
orientation: Orientation,
@@ -78,6 +82,7 @@
startDragImmediately: (startedPosition: Offset) -> Boolean,
onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
+ dispatcher: NestedScrollDispatcher,
): Modifier =
this.then(
MultiPointerDraggableElement(
@@ -86,6 +91,7 @@
startDragImmediately,
onDragStarted,
swipeDetector,
+ dispatcher,
)
)
@@ -96,6 +102,7 @@
private val onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
private val swipeDetector: SwipeDetector,
+ private val dispatcher: NestedScrollDispatcher,
) : ModifierNodeElement<MultiPointerDraggableNode>() {
override fun create(): MultiPointerDraggableNode =
MultiPointerDraggableNode(
@@ -104,6 +111,7 @@
startDragImmediately = startDragImmediately,
onDragStarted = onDragStarted,
swipeDetector = swipeDetector,
+ dispatcher = dispatcher,
)
override fun update(node: MultiPointerDraggableNode) {
@@ -122,11 +130,13 @@
var onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
var swipeDetector: SwipeDetector = DefaultSwipeDetector,
+ private val dispatcher: NestedScrollDispatcher,
) :
DelegatingNode(),
PointerInputModifierNode,
CompositionLocalConsumerModifierNode,
- ObserverModifierNode {
+ ObserverModifierNode,
+ SpaceVectorConverter {
private val pointerInputHandler: suspend PointerInputScope.() -> Unit = { pointerInput() }
private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
private val velocityTracker = VelocityTracker()
@@ -141,26 +151,22 @@
}
}
- private var _toFloat = orientation.toFunctionOffsetToFloat()
+ private var converter = SpaceVectorConverter(orientation)
- private fun Offset.toFloat(): Float = _toFloat(this)
+ override fun Offset.toFloat(): Float = with(converter) { [email protected]() }
- private fun Orientation.toFunctionOffsetToFloat(): (Offset) -> Float =
- when (this) {
- Orientation.Vertical -> {
- { it.y }
- }
- Orientation.Horizontal -> {
- { it.x }
- }
- }
+ override fun Velocity.toFloat(): Float = with(converter) { [email protected]() }
+
+ override fun Float.toOffset(): Offset = with(converter) { [email protected]() }
+
+ override fun Float.toVelocity(): Velocity = with(converter) { [email protected]() }
var orientation: Orientation = orientation
set(value) {
// Reset the pointer input whenever orientation changed.
if (value != field) {
field = value
- _toFloat = field.toFunctionOffsetToFloat()
+ converter = SpaceVectorConverter(value)
delegate.resetPointerInputHandler()
}
}
@@ -240,28 +246,32 @@
},
onDrag = { controller, change, amount ->
velocityTracker.addPointerInputChange(change)
- controller.onDrag(amount)
+ dispatchScrollEvents(
+ availableOnPreScroll = amount,
+ onScroll = { controller.onDrag(it) },
+ source = NestedScrollSource.UserInput,
+ )
},
onDragEnd = { controller ->
- val viewConfiguration = currentValueOf(LocalViewConfiguration)
- val maxVelocity =
- viewConfiguration.maximumFlingVelocity.let {
- Velocity(it, it)
- }
- val velocity = velocityTracker.calculateVelocity(maxVelocity)
- controller.onStop(
- velocity =
- when (orientation) {
- Orientation.Horizontal -> velocity.x
- Orientation.Vertical -> velocity.y
- },
- canChangeScene = true,
+ startFlingGesture(
+ initialVelocity =
+ currentValueOf(LocalViewConfiguration)
+ .maximumFlingVelocity
+ .let {
+ val maxVelocity = Velocity(it, it)
+ velocityTracker.calculateVelocity(maxVelocity)
+ }
+ .toFloat(),
+ onFling = { controller.onStop(it, canChangeScene = true) }
)
},
onDragCancel = { controller ->
- controller.onStop(velocity = 0f, canChangeScene = true)
+ startFlingGesture(
+ initialVelocity = 0f,
+ onFling = { controller.onStop(it, canChangeScene = true) }
+ )
},
- swipeDetector = swipeDetector
+ swipeDetector = swipeDetector,
)
} catch (exception: CancellationException) {
// If the coroutine scope is active, we can just restart the drag cycle.
@@ -276,6 +286,101 @@
}
/**
+ * Start a fling gesture in another CoroutineScope, this is to ensure that even when the pointer
+ * input scope is reset we will continue any coroutine scope that we started from these methods
+ * while the pointer input scope was active.
+ *
+ * Note: Inspired by [androidx.compose.foundation.gestures.ScrollableNode.onDragStopped]
+ */
+ private fun startFlingGesture(initialVelocity: Float, onFling: (velocity: Float) -> Float) {
+ // Note: [AwaitPointerEventScope] is annotated as @RestrictsSuspension, we need another
+ // CoroutineScope to run the fling gestures.
+ // We do not need to cancel this [Job], the source will take care of emitting an
+ // [onPostFling] before starting a new gesture.
+ dispatcher.coroutineScope.launch {
+ dispatchFlingEvents(availableOnPreFling = initialVelocity, onFling = onFling)
+ }
+ }
+
+ /**
+ * Use the nested scroll system to fire scroll events. This allows us to consume events from our
+ * ancestors during the pre-scroll and post-scroll phases.
+ *
+ * @param availableOnPreScroll amount available before the scroll, this can be partially
+ * consumed by our ancestors.
+ * @param onScroll function that returns the amount consumed during a scroll given the amount
+ * available after the [NestedScrollConnection.onPreScroll].
+ * @param source the source of the scroll event
+ * @return Total offset consumed.
+ */
+ private inline fun dispatchScrollEvents(
+ availableOnPreScroll: Float,
+ onScroll: (delta: Float) -> Float,
+ source: NestedScrollSource,
+ ): Float {
+ // PreScroll phase
+ val consumedByPreScroll =
+ dispatcher
+ .dispatchPreScroll(
+ available = availableOnPreScroll.toOffset(),
+ source = source,
+ )
+ .toFloat()
+
+ // Scroll phase
+ val availableOnScroll = availableOnPreScroll - consumedByPreScroll
+ val consumedBySelfScroll = onScroll(availableOnScroll)
+
+ // PostScroll phase
+ val availableOnPostScroll = availableOnScroll - consumedBySelfScroll
+ val consumedByPostScroll =
+ dispatcher
+ .dispatchPostScroll(
+ consumed = consumedBySelfScroll.toOffset(),
+ available = availableOnPostScroll.toOffset(),
+ source = source,
+ )
+ .toFloat()
+
+ return consumedByPreScroll + consumedBySelfScroll + consumedByPostScroll
+ }
+
+ /**
+ * Use the nested scroll system to fire fling events. This allows us to consume events from our
+ * ancestors during the pre-fling and post-fling phases.
+ *
+ * @param availableOnPreFling velocity available before the fling, this can be partially
+ * consumed by our ancestors.
+ * @param onFling function that returns the velocity consumed during the fling given the
+ * velocity available after the [NestedScrollConnection.onPreFling].
+ * @return Total velocity consumed.
+ */
+ private suspend inline fun dispatchFlingEvents(
+ availableOnPreFling: Float,
+ onFling: (velocity: Float) -> Float,
+ ): Float {
+ // PreFling phase
+ val consumedByPreFling =
+ dispatcher.dispatchPreFling(available = availableOnPreFling.toVelocity()).toFloat()
+
+ // Fling phase
+ val availableOnFling = availableOnPreFling - consumedByPreFling
+ val consumedBySelfFling = onFling(availableOnFling)
+
+ // PostFling phase
+ val availableOnPostFling = availableOnFling - consumedBySelfFling
+ val consumedByPostFling =
+ dispatcher
+ .dispatchPostFling(
+ consumed = consumedBySelfFling.toVelocity(),
+ available = availableOnPostFling.toVelocity(),
+ )
+ .toFloat()
+
+ return consumedByPreFling + consumedBySelfFling + consumedByPostFling
+ }
+
+ /**
* Detect drag gestures in the given [orientation].
*
* This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index b8010f2..a2118b2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -20,6 +20,7 @@
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
@@ -57,6 +58,7 @@
draggableHandler: DraggableHandlerImpl,
swipeDetector: SwipeDetector,
) : DelegatingNode(), PointerInputModifierNode {
+ private val dispatcher = NestedScrollDispatcher()
private val multiPointerDraggableNode =
delegate(
MultiPointerDraggableNode(
@@ -65,6 +67,7 @@
startDragImmediately = ::startDragImmediately,
onDragStarted = draggableHandler::onDragStarted,
swipeDetector = swipeDetector,
+ dispatcher = dispatcher,
)
)
@@ -93,7 +96,7 @@
)
init {
- delegate(nestedScrollModifierNode(nestedScrollHandlerImpl.connection, dispatcher = null))
+ delegate(nestedScrollModifierNode(nestedScrollHandlerImpl.connection, dispatcher))
delegate(ScrollBehaviorOwnerNode(draggableHandler.nestedScrollKey, nestedScrollHandlerImpl))
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index b98400a..2d37a0d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -28,6 +28,10 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputChange
@@ -37,6 +41,7 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.coroutineScope
@@ -49,17 +54,22 @@
class MultiPointerDraggableTest {
@get:Rule val rule = createComposeRule()
+ private val emptyConnection = object : NestedScrollConnection {}
+ private val defaultDispatcher = NestedScrollDispatcher()
+
+ private fun Modifier.nestedScrollDispatcher() = nestedScroll(emptyConnection, defaultDispatcher)
+
private class SimpleDragController(
- val onDrag: () -> Unit,
- val onStop: () -> Unit,
+ val onDrag: (delta: Float) -> Unit,
+ val onStop: (velocity: Float) -> Unit,
) : DragController {
override fun onDrag(delta: Float): Float {
- onDrag()
+ onDrag.invoke(delta)
return delta
}
override fun onStop(velocity: Float, canChangeScene: Boolean): Float {
- onStop()
+ onStop.invoke(velocity)
return velocity
}
}
@@ -79,6 +89,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { enabled },
@@ -90,6 +101,7 @@
onStop = { stopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
)
}
@@ -145,6 +157,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { true },
@@ -157,6 +170,7 @@
onStop = { stopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
.pointerInput(Unit) {
coroutineScope {
@@ -217,6 +231,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { true },
@@ -228,6 +243,7 @@
onStop = { stopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
) {
if (hasScrollable) {
@@ -335,6 +351,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { true },
@@ -346,6 +363,7 @@
onStop = { stopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
) {
Box(
@@ -436,6 +454,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { true },
@@ -447,6 +466,7 @@
onStop = { verticalStopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
.multiPointerDraggable(
orientation = Orientation.Horizontal,
@@ -459,6 +479,7 @@
onStop = { horizontalStopped = true },
)
},
+ dispatcher = defaultDispatcher,
)
)
}
@@ -539,6 +560,7 @@
touchSlop = LocalViewConfiguration.current.touchSlop
Box(
Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScrollDispatcher()
.multiPointerDraggable(
orientation = Orientation.Vertical,
enabled = { true },
@@ -557,6 +579,7 @@
onStop = { /* do nothing */ },
)
},
+ dispatcher = defaultDispatcher,
)
) {}
}
@@ -587,4 +610,113 @@
assertThat(started).isTrue()
}
+
+ @Test
+ fun multiPointerNestedScrollDispatcher() {
+ val size = 200f
+ val middle = Offset(size / 2f, size / 2f)
+ var touchSlop = 0f
+
+ var consumedOnPreScroll = 0f
+
+ var availableOnPreScroll = Float.MIN_VALUE
+ var availableOnPostScroll = Float.MIN_VALUE
+ var availableOnPreFling = Float.MIN_VALUE
+ var availableOnPostFling = Float.MIN_VALUE
+
+ var consumedOnDrag = 0f
+ var consumedOnDragStop = 0f
+
+ val connection =
+ object : NestedScrollConnection {
+ override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+ availableOnPreScroll = available.y
+ return Offset(0f, consumedOnPreScroll)
+ }
+
+ override fun onPostScroll(
+ consumed: Offset,
+ available: Offset,
+ source: NestedScrollSource
+ ): Offset {
+ availableOnPostScroll = available.y
+ return Offset.Zero
+ }
+
+ override suspend fun onPreFling(available: Velocity): Velocity {
+ availableOnPreFling = available.y
+ return Velocity.Zero
+ }
+
+ override suspend fun onPostFling(
+ consumed: Velocity,
+ available: Velocity
+ ): Velocity {
+ availableOnPostFling = available.y
+ return Velocity.Zero
+ }
+ }
+
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ Box(
+ Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .nestedScroll(connection)
+ .nestedScrollDispatcher()
+ .multiPointerDraggable(
+ orientation = Orientation.Vertical,
+ enabled = { true },
+ startDragImmediately = { false },
+ onDragStarted = { _, _, _ ->
+ SimpleDragController(
+ onDrag = { consumedOnDrag = it },
+ onStop = { consumedOnDragStop = it },
+ )
+ },
+ dispatcher = defaultDispatcher,
+ )
+ )
+ }
+
+ fun startDrag() {
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop))
+ }
+ }
+
+ fun continueDrag() {
+ rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) }
+ }
+
+ fun stopDrag() {
+ rule.onRoot().performTouchInput { up() }
+ }
+
+ startDrag()
+
+ continueDrag()
+ assertThat(availableOnPreScroll).isEqualTo(touchSlop)
+ assertThat(consumedOnDrag).isEqualTo(touchSlop)
+ assertThat(availableOnPostScroll).isEqualTo(0f)
+
+ // Parent node consumes half of the gesture
+ consumedOnPreScroll = touchSlop / 2f
+ continueDrag()
+ assertThat(availableOnPreScroll).isEqualTo(touchSlop)
+ assertThat(consumedOnDrag).isEqualTo(touchSlop / 2f)
+ assertThat(availableOnPostScroll).isEqualTo(0f)
+
+ // Parent node consumes the gesture
+ consumedOnPreScroll = touchSlop
+ continueDrag()
+ assertThat(availableOnPreScroll).isEqualTo(touchSlop)
+ assertThat(consumedOnDrag).isEqualTo(0f)
+ assertThat(availableOnPostScroll).isEqualTo(0f)
+
+ // Parent node can intercept the velocity on stop
+ stopDrag()
+ assertThat(availableOnPreFling).isEqualTo(consumedOnDragStop)
+ assertThat(availableOnPostFling).isEqualTo(0f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
new file mode 100644
index 0000000..4850085
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerFullscreenSwipeTouchHandlerTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.ambient.touch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.ValueAnimator;
+import android.content.pm.UserInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.view.GestureDetector.OnGestureListener;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.systemui.Flags;
+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.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.FakeUserTracker;
+import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+@DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
+public class BouncerFullscreenSwipeTouchHandlerTest extends SysuiTestCase {
+ private KosmosJavaAdapter mKosmos;
+
+ @Mock
+ CentralSurfaces mCentralSurfaces;
+
+ @Mock
+ ScrimManager mScrimManager;
+
+ @Mock
+ ScrimController mScrimController;
+
+ @Mock
+ NotificationShadeWindowController mNotificationShadeWindowController;
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtils;
+
+ @Mock
+ FlingAnimationUtils mFlingAnimationUtilsClosing;
+
+ @Mock
+ TouchHandler.TouchSession mTouchSession;
+
+ BouncerSwipeTouchHandler mTouchHandler;
+
+ @Mock
+ BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator;
+
+ @Mock
+ ValueAnimator mValueAnimator;
+
+ @Mock
+ BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory;
+
+ @Mock
+ VelocityTracker mVelocityTracker;
+
+ @Mock
+ UiEventLogger mUiEventLogger;
+
+ @Mock
+ LockPatternUtils mLockPatternUtils;
+
+ @Mock
+ ActivityStarter mActivityStarter;
+
+ @Mock
+ CommunalViewModel mCommunalViewModel;
+
+ FakeUserTracker mUserTracker;
+
+ private static final float TOUCH_REGION = .3f;
+ private static final float MIN_BOUNCER_HEIGHT = .05f;
+
+ private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
+ private static final UserInfo CURRENT_USER_INFO = new UserInfo(
+ 10,
+ /* name= */ "user10",
+ /* flags= */ 0
+ );
+
+ @Before
+ public void setup() {
+ mKosmos = new KosmosJavaAdapter(this);
+ MockitoAnnotations.initMocks(this);
+ mUserTracker = new FakeUserTracker();
+ mTouchHandler = new BouncerSwipeTouchHandler(
+ mKosmos.getTestScope(),
+ mScrimManager,
+ Optional.of(mCentralSurfaces),
+ mNotificationShadeWindowController,
+ mValueAnimatorCreator,
+ mVelocityTrackerFactory,
+ mLockPatternUtils,
+ mUserTracker,
+ mCommunalViewModel,
+ mFlingAnimationUtils,
+ mFlingAnimationUtilsClosing,
+ TOUCH_REGION,
+ MIN_BOUNCER_HEIGHT,
+ mUiEventLogger,
+ mActivityStarter);
+
+ when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
+ when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
+ when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
+ when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
+ when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
+ when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
+
+ mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
+ }
+
+ /**
+ * Ensures expansion does not happen for full vertical swipes when touch is not available.
+ */
+ @Test
+ public void testFullSwipe_notInitiatedWhenNotAvailable() {
+ mTouchHandler.onGlanceableTouchAvailable(false);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isFalse();
+ }
+
+ /**
+ * Ensures expansion only happens for full vertical swipes when touch is available.
+ */
+ @Test
+ public void testFullSwipe_initiatedWhenAvailable() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+ }
+
+ @Test
+ public void testFullSwipe_motionUpResetsTouchState() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ ArgumentCaptor<InputChannelCompat.InputEventListener> inputListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(inputListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+
+ MotionEvent upEvent = Mockito.mock(MotionEvent.class);
+ when(upEvent.getAction()).thenReturn(MotionEvent.ACTION_UP);
+ inputListenerCaptor.getValue().onInputEvent(upEvent);
+ verify(mCommunalViewModel).onResetTouchState();
+ }
+
+ @Test
+ public void testFullSwipe_motionCancelResetsTouchState() {
+ mTouchHandler.onGlanceableTouchAvailable(true);
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<OnGestureListener> gestureListenerCaptor =
+ ArgumentCaptor.forClass(OnGestureListener.class);
+ ArgumentCaptor<InputChannelCompat.InputEventListener> inputListenerCaptor =
+ ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+ verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
+ verify(mTouchSession).registerInputListener(inputListenerCaptor.capture());
+
+ // A touch within range at the bottom of the screen should trigger listening
+ assertThat(gestureListenerCaptor.getValue()
+ .onScroll(Mockito.mock(MotionEvent.class),
+ Mockito.mock(MotionEvent.class),
+ 1,
+ 2)).isTrue();
+
+ MotionEvent upEvent = Mockito.mock(MotionEvent.class);
+ when(upEvent.getAction()).thenReturn(MotionEvent.ACTION_CANCEL);
+ inputListenerCaptor.getValue().onInputEvent(upEvent);
+ verify(mCommunalViewModel).onResetTouchState();
+ }
+}
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 7ebc224..0e98b84 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
@@ -50,6 +50,8 @@
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.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -72,7 +74,9 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
+ private KosmosJavaAdapter mKosmos;
@Mock
CentralSurfaces mCentralSurfaces;
@@ -120,6 +124,9 @@
@Mock
Region mRegion;
+ @Mock
+ CommunalViewModel mCommunalViewModel;
+
@Captor
ArgumentCaptor<Rect> mRectCaptor;
@@ -139,9 +146,11 @@
@Before
public void setup() {
+ mKosmos = new KosmosJavaAdapter(this);
MockitoAnnotations.initMocks(this);
mUserTracker = new FakeUserTracker();
mTouchHandler = new BouncerSwipeTouchHandler(
+ mKosmos.getTestScope(),
mScrimManager,
Optional.of(mCentralSurfaces),
mNotificationShadeWindowController,
@@ -149,6 +158,7 @@
mVelocityTrackerFactory,
mLockPatternUtils,
mUserTracker,
+ mCommunalViewModel,
mFlingAnimationUtils,
mFlingAnimationUtilsClosing,
TOUCH_REGION,
@@ -201,7 +211,6 @@
2)).isTrue();
}
-
/**
* Ensures expansion only happens when touch down happens in valid part of the screen.
*/
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index 7fd9ce2..204d4b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -26,8 +26,10 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler.TouchSession
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.shared.system.InputChannelCompat
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -50,11 +52,11 @@
@RunWith(AndroidJUnit4::class)
class ShadeTouchHandlerTest : SysuiTestCase() {
private var kosmos = testKosmos()
-
private var mCentralSurfaces = mock<CentralSurfaces>()
private var mShadeViewController = mock<ShadeViewController>()
private var mDreamManager = mock<DreamManager>()
private var mTouchSession = mock<TouchSession>()
+ private var communalViewModel = mock<CommunalViewModel>()
private lateinit var mTouchHandler: ShadeTouchHandler
@@ -65,9 +67,11 @@
fun setup() {
mTouchHandler =
ShadeTouchHandler(
+ kosmos.testScope,
Optional.of(mCentralSurfaces),
mShadeViewController,
mDreamManager,
+ communalViewModel,
kosmos.communalSettingsInteractor,
TOUCH_HEIGHT
)
@@ -75,6 +79,7 @@
// Verifies that a swipe down in the gesture region is captured by the shade touch handler.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_captured() {
val captured = swipe(Direction.DOWN)
Truth.assertThat(captured).isTrue()
@@ -82,6 +87,7 @@
// Verifies that a swipe in the upward direction is not captured.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeUp_notCaptured() {
val captured = swipe(Direction.UP)
@@ -91,6 +97,7 @@
// Verifies that a swipe down forwards captured touches to central surfaces for handling.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeDown_communalEnabled_sentToCentralSurfaces() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -103,7 +110,7 @@
// Verifies that a swipe down forwards captured touches to the shade view for handling.
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_communalDisabled_sentToShadeView() {
swipe(Direction.DOWN)
@@ -114,6 +121,7 @@
// Verifies that a swipe down while dreaming forwards captured touches to the shade view for
// handling.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeDown_dreaming_sentToShadeView() {
whenever(mDreamManager.isDreaming).thenReturn(true)
swipe(Direction.DOWN)
@@ -124,6 +132,7 @@
// Verifies that a swipe up is not forwarded to central surfaces.
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
@EnableFlags(Flags.FLAG_COMMUNAL_HUB)
fun testSwipeUp_communalEnabled_touchesNotSent() {
kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true)
@@ -137,7 +146,7 @@
// Verifies that a swipe up is not forwarded to the shade view.
@Test
- @DisableFlags(Flags.FLAG_COMMUNAL_HUB)
+ @DisableFlags(Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testSwipeUp_communalDisabled_touchesNotSent() {
swipe(Direction.UP)
@@ -147,6 +156,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
fun testCancelMotionEvent_popsTouchSession() {
swipe(Direction.DOWN)
val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0)
@@ -154,6 +164,60 @@
verify(mTouchSession).pop()
}
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_initiatedWhenAvailable() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ val captured = swipe(Direction.DOWN)
+ Truth.assertThat(captured).isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_notInitiatedWhenNotAvailable() {
+ // Indicate touches aren't available
+ mTouchHandler.onGlanceableTouchAvailable(false)
+
+ // Verify swipe is not handled
+ val captured = swipe(Direction.DOWN)
+ Truth.assertThat(captured).isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_resetsTouchStateOnUp() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ swipe(Direction.DOWN)
+
+ val upEvent: MotionEvent = mock()
+ whenever(upEvent.action).thenReturn(MotionEvent.ACTION_UP)
+ mInputListenerCaptor.lastValue.onInputEvent(upEvent)
+
+ verify(communalViewModel).onResetTouchState()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE)
+ fun testFullVerticalSwipe_resetsTouchStateOnCancel() {
+ // Indicate touches are available
+ mTouchHandler.onGlanceableTouchAvailable(true)
+
+ // Verify swipe is handled
+ swipe(Direction.DOWN)
+
+ val upEvent: MotionEvent = mock()
+ whenever(upEvent.action).thenReturn(MotionEvent.ACTION_CANCEL)
+ mInputListenerCaptor.lastValue.onInputEvent(upEvent)
+
+ verify(communalViewModel).onResetTouchState()
+ }
+
/**
* Simulates a swipe in the given direction and returns true if the touch was intercepted by the
* touch handler's gesture listener.
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 242e822..e2a6a55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -35,6 +35,8 @@
import android.view.accessibility.AccessibilityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.app.viewcapture.ViewCapture
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
@@ -70,6 +72,7 @@
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -108,6 +111,7 @@
@Mock private lateinit var inflater: LayoutInflater
@Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var lazyViewCapture: kotlin.Lazy<ViewCapture>
@Mock private lateinit var accessibilityManager: AccessibilityManager
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@@ -192,7 +196,8 @@
UdfpsControllerOverlay(
context,
inflater,
- windowManager,
+ ViewCaptureAwareWindowManager(windowManager, lazyViewCapture,
+ isViewCaptureEnabled = false),
accessibilityManager,
statusBarStateController,
statusBarKeyguardViewManager,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 54e0725..d86890b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -65,12 +65,12 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
-import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -87,6 +87,7 @@
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.dump.DumpManager;
@@ -118,6 +119,8 @@
import dagger.Lazy;
+import javax.inject.Provider;
+
import kotlinx.coroutines.CoroutineScope;
import org.junit.Before;
@@ -152,7 +155,7 @@
@Mock
private FingerprintManager mFingerprintManager;
@Mock
- private WindowManager mWindowManager;
+ private ViewCaptureAwareWindowManager mWindowManager;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
@@ -261,6 +264,8 @@
private Lazy<DeviceEntryUdfpsTouchOverlayViewModel> mDeviceEntryUdfpsTouchOverlayViewModel;
@Mock
private Lazy<DefaultUdfpsTouchOverlayViewModel> mDefaultUdfpsTouchOverlayViewModel;
+ @Mock
+ private Provider<CameraGestureHelper> mCameraGestureHelper;
@Before
public void setUp() {
@@ -269,7 +274,8 @@
mPowerRepository,
mock(FalsingCollector.class),
mock(ScreenOffAnimationController.class),
- mStatusBarStateController
+ mStatusBarStateController,
+ mCameraGestureHelper
);
mPowerRepository.updateWakefulness(
WakefulnessState.AWAKE,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index c28cf34..a09189e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -372,6 +372,7 @@
nonAuxiliarySubtypes: Int,
): InputMethodModel {
return InputMethodModel(
+ userId = UUID.randomUUID().mostSignificantBits.toInt(),
imeId = UUID.randomUUID().toString(),
subtypes =
List(auxiliarySubtypes + nonAuxiliarySubtypes) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
similarity index 67%
rename from packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
index bea0db6..a0928ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager
import android.app.IActivityTaskManager
+import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.ContentResolver
import android.content.Intent
@@ -29,82 +30,73 @@
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mock
-import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class CameraGestureHelperTest : SysuiTestCase() {
- @Mock
- lateinit var centralSurfaces: CentralSurfaces
- @Mock
- lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
- @Mock
- lateinit var keyguardStateController: KeyguardStateController
- @Mock
- lateinit var packageManager: PackageManager
- @Mock
- lateinit var activityManager: ActivityManager
- @Mock
- lateinit var activityStarter: ActivityStarter
- @Mock
- lateinit var activityIntentHelper: ActivityIntentHelper
- @Mock
- lateinit var activityTaskManager: IActivityTaskManager
- @Mock
- lateinit var cameraIntents: CameraIntentsWrapper
- @Mock
- lateinit var contentResolver: ContentResolver
- @Mock
- lateinit var mSelectedUserInteractor: SelectedUserInteractor
+ @Mock lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock lateinit var keyguardStateController: KeyguardStateController
+ @Mock lateinit var packageManager: PackageManager
+ @Mock lateinit var activityManager: ActivityManager
+ @Mock lateinit var activityStarter: ActivityStarter
+ @Mock lateinit var activityIntentHelper: ActivityIntentHelper
+ @Mock lateinit var activityTaskManager: IActivityTaskManager
+ @Mock lateinit var cameraIntents: CameraIntentsWrapper
+ @Mock lateinit var contentResolver: ContentResolver
+ @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor
+ @Mock lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock lateinit var lockscreenUserManager: NotificationLockscreenUserManager
private lateinit var underTest: CameraGestureHelper
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(cameraIntents.getSecureCameraIntent(anyInt())).thenReturn(
- Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION)
- )
- whenever(cameraIntents.getInsecureCameraIntent(anyInt())).thenReturn(
- Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
- )
+ whenever(cameraIntents.getSecureCameraIntent(any()))
+ .thenReturn(Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION))
+ whenever(cameraIntents.getInsecureCameraIntent(any()))
+ .thenReturn(Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION))
prepare()
- underTest = CameraGestureHelper(
- context = mock(),
- centralSurfaces = centralSurfaces,
- keyguardStateController = keyguardStateController,
- statusBarKeyguardViewManager = statusBarKeyguardViewManager,
- packageManager = packageManager,
- activityManager = activityManager,
- activityStarter = activityStarter,
- activityIntentHelper = activityIntentHelper,
- activityTaskManager = activityTaskManager,
- cameraIntents = cameraIntents,
- contentResolver = contentResolver,
- uiExecutor = MoreExecutors.directExecutor(),
- selectedUserInteractor = mSelectedUserInteractor,
- )
+ underTest =
+ CameraGestureHelper(
+ context = mock(),
+ keyguardStateController = keyguardStateController,
+ statusBarKeyguardViewManager = statusBarKeyguardViewManager,
+ packageManager = packageManager,
+ activityManager = activityManager,
+ activityStarter = activityStarter,
+ activityIntentHelper = activityIntentHelper,
+ activityTaskManager = activityTaskManager,
+ cameraIntents = cameraIntents,
+ contentResolver = contentResolver,
+ uiExecutor = MoreExecutors.directExecutor(),
+ selectedUserInteractor = mSelectedUserInteractor,
+ devicePolicyManager = devicePolicyManager,
+ lockscreenUserManager = lockscreenUserManager,
+ )
}
/**
@@ -116,13 +108,13 @@
* @param isCameraAllowedByAdmin Whether the device administrator allows use of the camera app
* @param installedCameraAppCount The number of installed camera apps on the device
* @param isUsingSecureScreenLockOption Whether the user-controlled setting for Screen Lock is
- * set with a "secure" option that requires the user to provide some secret/credentials to be
- * able to unlock the device, for example "Face Unlock", "PIN", or "Password". Examples of
- * non-secure options are "None" and "Swipe"
+ * set with a "secure" option that requires the user to provide some secret/credentials to be
+ * able to unlock the device, for example "Face Unlock", "PIN", or "Password". Examples of
+ * non-secure options are "None" and "Swipe"
* @param isCameraActivityRunningOnTop Whether the camera activity is running at the top of the
- * most recent/current task of activities
+ * most recent/current task of activities
* @param isTaskListEmpty Whether there are no active activity tasks at all. Note that this is
- * treated as `false` if [isCameraActivityRunningOnTop] is set to `true`
+ * treated as `false` if [isCameraActivityRunningOnTop] is set to `true`
*/
private fun prepare(
isCameraAllowedByAdmin: Boolean = true,
@@ -131,7 +123,13 @@
isCameraActivityRunningOnTop: Boolean = false,
isTaskListEmpty: Boolean = false,
) {
- whenever(centralSurfaces.isCameraAllowedByAdmin).thenReturn(isCameraAllowedByAdmin)
+ whenever(lockscreenUserManager.getCurrentUserId()).thenReturn(1)
+ if (isCameraAllowedByAdmin) {
+ whenever(devicePolicyManager.getCameraDisabled(isNull(), any())).thenReturn(false)
+ whenever(keyguardStateController.isMethodSecure).thenReturn(false)
+ } else {
+ whenever(devicePolicyManager.getCameraDisabled(isNull(), any())).thenReturn(true)
+ }
whenever(activityIntentHelper.wouldLaunchResolverActivity(any(), anyInt()))
.thenReturn(installedCameraAppCount > 1)
@@ -141,30 +139,26 @@
.thenReturn(!isUsingSecureScreenLockOption)
if (installedCameraAppCount >= 1) {
- val resolveInfo = ResolveInfo().apply {
- this.activityInfo = ActivityInfo().apply {
- packageName = CAMERA_APP_PACKAGE_NAME
+ val resolveInfo =
+ ResolveInfo().apply {
+ this.activityInfo =
+ ActivityInfo().apply { packageName = CAMERA_APP_PACKAGE_NAME }
}
- }
- whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
- resolveInfo
- )
+ whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(resolveInfo)
} else {
- whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
- null
- )
+ whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(null)
}
when {
isCameraActivityRunningOnTop -> {
- val runningTaskInfo = ActivityManager.RunningTaskInfo().apply {
- topActivity = ComponentName(CAMERA_APP_PACKAGE_NAME, "cameraActivity")
- }
- whenever(activityManager.getRunningTasks(anyInt())).thenReturn(
- listOf(
- runningTaskInfo
- )
- )
+ val runningTaskInfo =
+ ActivityManager.RunningTaskInfo().apply {
+ topActivity = ComponentName(CAMERA_APP_PACKAGE_NAME, "cameraActivity")
+ }
+ whenever(activityManager.getRunningTasks(anyInt()))
+ .thenReturn(listOf(runningTaskInfo))
}
isTaskListEmpty -> {
whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList())
@@ -289,28 +283,28 @@
) {
val intentCaptor = KotlinArgumentCaptor(Intent::class.java)
if (isSecure && !moreThanOneCameraAppInstalled) {
- verify(activityTaskManager).startActivityAsUser(
- any(),
- any(),
- any(),
- intentCaptor.capture(),
- any(),
- any(),
- any(),
- anyInt(),
- anyInt(),
- any(),
- any(),
- anyInt()
- )
+ verify(activityTaskManager)
+ .startActivityAsUser(
+ isNull(),
+ isNull(),
+ isNull(),
+ intentCaptor.capture(),
+ isNull(),
+ isNull(),
+ isNull(),
+ anyInt(),
+ anyInt(),
+ isNull(),
+ any(),
+ anyInt()
+ )
} else {
verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
}
val intent = intentCaptor.value
assertThat(CameraIntents.isSecureCameraIntent(intent)).isEqualTo(isSecure)
- assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1))
- .isEqualTo(source)
+ assertThat(intent.getIntExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, -1)).isEqualTo(source)
}
companion object {
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 86871ed..7a41bc6 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
@@ -756,6 +756,17 @@
verify(metricsLogger).logTapWidget("test_pkg/test_cls", rank = 10)
}
+ @Test
+ fun glanceableTouchAvailable_availableWhenNestedScrollingWithoutConsumption() =
+ testScope.runTest {
+ val touchAvailable by collectLastValue(underTest.glanceableTouchAvailable)
+ assertThat(touchAvailable).isTrue()
+ underTest.onHubTouchConsumed()
+ assertThat(touchAvailable).isFalse()
+ underTest.onNestedScrolling()
+ assertThat(touchAvailable).isTrue()
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
val user = if (isMainUser) MAIN_USER_INFO else SECONDARY_USER_INFO
with(userRepository) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 2bf50b3..91259a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -58,7 +58,6 @@
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -78,7 +77,6 @@
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.statusbar.commandQueue
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
@@ -116,7 +114,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
- private val kosmos = testKosmos().apply { this.commandQueue = this.fakeCommandQueue }
+ private val kosmos = testKosmos()
private lateinit var underTest: DeviceEntryFaceAuthRepositoryImpl
@Mock private lateinit var faceManager: FaceManager
@@ -162,7 +160,6 @@
private val displayStateInteractor = kosmos.displayStateInteractor
private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
private val displayRepository = kosmos.displayRepository
- private val fakeCommandQueue = kosmos.fakeCommandQueue
private val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
private lateinit var featureFlags: FakeFeatureFlags
@@ -572,9 +569,7 @@
bouncerRepository.setAlternateVisible(false)
// Keyguard is occluded when secure camera is active.
keyguardRepository.setKeyguardOccluded(true)
- fakeCommandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
- }
+ keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
}
}
@@ -589,9 +584,7 @@
assertThat(canFaceAuthRun()).isTrue()
// launch secure camera
- fakeCommandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
- }
+ keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
keyguardRepository.setKeyguardOccluded(true)
runCurrent()
assertThat(canFaceAuthRun()).isFalse()
@@ -870,9 +863,7 @@
bouncerRepository.setAlternateVisible(false)
// Keyguard is occluded when secure camera is active.
keyguardRepository.setKeyguardOccluded(true)
- fakeCommandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
- }
+ keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
}
}
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 18839e6..273e3cb 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
@@ -23,9 +23,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
-import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.qs.qsTileFactory
import com.android.systemui.statusbar.policy.keyguardStateController
import com.android.systemui.testKosmos
@@ -73,7 +73,7 @@
QSLongPressEffect(
vibratorHelper,
kosmos.keyguardStateController,
- kosmos.falsingManager,
+ FakeLogBuffer.Factory.create(),
)
longPressEffect.callback = callback
longPressEffect.qsTile = qsTile
@@ -304,13 +304,12 @@
}
@Test
- fun getStateForClick_withFalseTapWhenLocked_returnsIdle() {
+ fun getStateForClick_whenKeyguardsIsShowing_returnsIdle() {
// GIVEN an active tile
qsTile.state?.state = Tile.STATE_ACTIVE
- // GIVEN that the device is locked and a false tap is detected
- whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
- kosmos.falsingManager.setFalseTap(true)
+ // GIVEN that the keyguard is showing
+ whenever(kosmos.keyguardStateController.isShowing).thenReturn(true)
// WHEN determining the state of a click action
val clickState = longPressEffect.getStateForClick()
@@ -324,9 +323,8 @@
// GIVEN an active tile
qsTile.state?.state = Tile.STATE_ACTIVE
- // GIVEN that the device is locked and a false tap is not detected
- whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
- kosmos.falsingManager.setFalseTap(false)
+ // GIVEN that the keyguard is not showing
+ whenever(kosmos.keyguardStateController.isShowing).thenReturn(false)
// WHEN determining the state of a click action
val clickState = longPressEffect.getStateForClick()
@@ -340,9 +338,8 @@
// GIVEN that the tile is null
longPressEffect.qsTile = null
- // GIVEN that the device is locked and a false tap is not detected
- whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
- kosmos.falsingManager.setFalseTap(false)
+ // GIVEN that the keyguard is not showing
+ whenever(kosmos.keyguardStateController.isShowing).thenReturn(false)
// WHEN determining the state of a click action
val clickState = longPressEffect.getStateForClick()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
index 857cdce..274880b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/data/repository/InputMethodRepositoryTest.kt
@@ -56,9 +56,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
- .thenReturn(listOf())
-
underTest =
InputMethodRepositoryImpl(
backgroundDispatcher = kosmos.testDispatcher,
@@ -71,10 +68,16 @@
testScope.runTest {
whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
.thenReturn(listOf())
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
+ whenever(
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ any(),
+ anyBoolean(),
+ eq(USER_HANDLE)
+ )
+ )
.thenReturn(listOf())
- assertThat(underTest.enabledInputMethods(USER_ID, fetchSubtypes = true).count())
+ assertThat(underTest.enabledInputMethods(USER_HANDLE, fetchSubtypes = true).count())
.isEqualTo(0)
}
@@ -83,11 +86,20 @@
testScope.runTest {
val subtypeId = 123
val isAuxiliary = true
+ val selectedImiId = "imiId"
+ val selectedImi = mock<InputMethodInfo>()
+ whenever(selectedImi.id).thenReturn(selectedImiId)
+ whenever(inputMethodManager.getCurrentInputMethodInfoAsUser(eq(USER_HANDLE)))
+ .thenReturn(selectedImi)
whenever(inputMethodManager.getEnabledInputMethodListAsUser(eq(USER_HANDLE)))
- .thenReturn(listOf(mock<InputMethodInfo>()))
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(any(), anyBoolean()))
- .thenReturn(listOf())
- whenever(inputMethodManager.getEnabledInputMethodSubtypeList(eq(null), anyBoolean()))
+ .thenReturn(listOf(selectedImi))
+ whenever(
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ eq(selectedImiId),
+ anyBoolean(),
+ eq(USER_HANDLE)
+ )
+ )
.thenReturn(
listOf(
InputMethodSubtype.InputMethodSubtypeBuilder()
@@ -97,7 +109,7 @@
)
)
- val result = underTest.selectedInputMethodSubtypes()
+ val result = underTest.selectedInputMethodSubtypes(USER_HANDLE)
assertThat(result).hasSize(1)
assertThat(result.first().subtypeId).isEqualTo(subtypeId)
assertThat(result.first().isAuxiliary).isEqualTo(isAuxiliary)
@@ -108,7 +120,7 @@
testScope.runTest {
val displayId = 7
- underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes = */ true)
+ underTest.showInputMethodPicker(displayId, /* showAuxiliarySubtypes= */ true)
verify(inputMethodManager)
.showInputMethodPickerFromSystem(
@@ -118,7 +130,6 @@
}
companion object {
- private const val USER_ID = 100
- private val USER_HANDLE = UserHandle.of(USER_ID)
+ private val USER_HANDLE = UserHandle.of(100)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
index d23ff2a..8e6de2f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractorTest.kt
@@ -143,6 +143,7 @@
nonAuxiliarySubtypes: Int,
): InputMethodModel {
return InputMethodModel(
+ userId = UUID.randomUUID().mostSignificantBits.toInt(),
imeId = UUID.randomUUID().toString(),
subtypes =
List(auxiliarySubtypes + nonAuxiliarySubtypes) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index 5115f5a..3b8ffcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -173,6 +173,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun transitionToGone_whenOpeningGlanceableHubEditMode() =
testScope.runTest {
kosmos.fakeKeyguardBouncerRepository.setAlternateVisible(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
index 61ccd7f..7424320 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -32,11 +32,13 @@
package com.android.systemui.keyguard.domain.interactor
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -108,6 +110,7 @@
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() =
testScope.runTest {
kosmos.fakeCommunalSceneRepository.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 7906a82..fc827a14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -32,7 +32,7 @@
import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.keyguard.shared.model.CameraLaunchType
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -47,7 +47,6 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -87,31 +86,17 @@
val cameraLaunchSource = collectLastValue(flow)
runCurrent()
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)
- }
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE)
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+ assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.POWER_DOUBLE_TAP)
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
- )
- }
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP)
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE)
+ assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.WIGGLE)
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)
- }
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER)
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER)
+ assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.LIFT_TRIGGER)
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE
- )
- }
- assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE)
-
- flow.onCompletion { assertThat(commandQueue.callbackCount()).isEqualTo(0) }
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ assertThat(cameraLaunchSource()!!.type).isEqualTo(CameraLaunchType.QUICK_AFFORDANCE)
}
@Test
@@ -121,11 +106,7 @@
val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
runCurrent()
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
- )
- }
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
assertThat(secureCameraActive()).isTrue()
@@ -146,11 +127,7 @@
val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
runCurrent()
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
- )
- }
+ underTest.onCameraLaunchDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
assertThat(secureCameraActive()).isTrue()
// Keyguard is showing and not occluded
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 2d77f4f..75c0d3b 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
@@ -22,6 +22,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
@@ -86,7 +87,8 @@
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var shadeInteractor: ShadeInteractor
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
private val kosmos = testKosmos()
@@ -194,6 +196,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index d9708a4..9762fd8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
-import android.app.StatusBarManager
+import android.app.StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
@@ -42,7 +42,6 @@
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
@@ -59,7 +58,6 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.shadeTestUtil
-import com.android.systemui.statusbar.commandQueue
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -93,13 +91,12 @@
private val kosmos =
testKosmos().apply {
fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
- this.commandQueue = fakeCommandQueue
}
private val testScope = kosmos.testScope
private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
- private var commandQueue = kosmos.fakeCommandQueue
private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private lateinit var featureFlags: FakeFeatureFlags
@@ -1724,11 +1721,7 @@
reset(transitionRepository)
// ...AND WHEN the camera gesture is detected quickly afterwards
- commandQueue.doForEachCallback {
- it.onCameraLaunchGestureDetected(
- StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
- )
- }
+ keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
runCurrent()
// THEN a transition from DOZING => OCCLUDED should occur
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt
index aba21c9..cd0a11c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.Configuration
+import android.util.LayoutDirection
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -33,6 +35,8 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -73,6 +77,9 @@
R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x,
-100
)
+ val configuration: Configuration = mock()
+ whenever(configuration.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(configuration)
val values by collectValues(underTest.dreamOverlayTranslationX)
assertThat(values).isEmpty()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelTest.kt
index 11890c7..69361ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.Configuration
+import android.util.LayoutDirection
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -33,6 +35,8 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -69,6 +73,9 @@
@Test
fun dreamOverlayTranslationX() =
testScope.runTest {
+ val config: Configuration = mock()
+ whenever(config.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(config)
configurationRepository.setDimensionPixelSize(
R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x,
100
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
index 1aa1ec4..d2be649 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.Configuration
+import android.util.LayoutDirection
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -34,6 +36,8 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -77,6 +81,10 @@
@Test
fun lockscreenTranslationX() =
testScope.runTest {
+ val config: Configuration = mock()
+ whenever(config.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(config)
+
configurationRepository.setDimensionPixelSize(
R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
100
@@ -102,6 +110,10 @@
@Test
fun lockscreenTranslationX_resetsAfterCancellation() =
testScope.runTest {
+ val config: Configuration = mock()
+ whenever(config.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(config)
+
configurationRepository.setDimensionPixelSize(
R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
100
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 3db9ef1..bca83f0 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
@@ -257,7 +257,6 @@
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
return LockscreenSceneViewModel(
- applicationScope = testScope.backgroundScope,
deviceEntryInteractor = kosmos.deviceEntryInteractor,
communalInteractor = kosmos.communalInteractor,
touchHandling =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt
index 68a7b7e3..a60a486 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.Configuration
+import android.util.LayoutDirection
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -34,6 +36,9 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -82,6 +87,9 @@
R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x,
-100
)
+ val configuration = mock<Configuration>()
+ whenever(configuration.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(configuration)
val values by collectValues(underTest.keyguardTranslationX)
assertThat(values).isEmpty()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
similarity index 76%
rename from packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
index 12c9eb9..53dec69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
@@ -21,22 +21,23 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.cameraGestureHelper
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.power.shared.model.WakeSleepReason
-import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
-import kotlin.test.assertEquals
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,6 +48,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class PowerInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val cameraGestureHelper = kosmos.cameraGestureHelper
private lateinit var underTest: PowerInteractor
private lateinit var repository: FakePowerRepository
@@ -66,33 +70,30 @@
falsingCollector,
screenOffAnimationController,
statusBarStateController,
+ { cameraGestureHelper },
)
+
+ whenever(cameraGestureHelper.canCameraGestureBeLaunched(any())).thenReturn(true)
}
@Test
fun isInteractive_screenTurnsOff() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
repository.setInteractive(true)
- var value: Boolean? = null
- val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
+ val isInteractive by collectLastValue(underTest.isInteractive)
repository.setInteractive(false)
-
- assertThat(value).isFalse()
- job.cancel()
+ assertThat(isInteractive).isFalse()
}
@Test
fun isInteractive_becomesInteractive() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
repository.setInteractive(false)
- var value: Boolean? = null
- val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
+ val isInteractive by collectLastValue(underTest.isInteractive)
repository.setInteractive(true)
-
- assertThat(value).isTrue()
- job.cancel()
+ assertThat(isInteractive).isTrue()
}
@Test
@@ -203,6 +204,23 @@
}
@Test
+ fun onCameraLaunchGestureDetected_isNotTrueWhenCannotLaunch() {
+ whenever(cameraGestureHelper.canCameraGestureBeLaunched(any())).thenReturn(false)
+ underTest.onStartedWakingUp(
+ PowerManager.WAKE_REASON_POWER_BUTTON,
+ /*powerButtonLaunchGestureTriggeredDuringSleep= */ false
+ )
+ underTest.onFinishedWakingUp()
+ underTest.onCameraLaunchGestureDetected()
+
+ assertThat(repository.wakefulness.value.internalWakefulnessState)
+ .isEqualTo(WakefulnessState.AWAKE)
+ assertThat(repository.wakefulness.value.lastWakeReason)
+ .isEqualTo(WakeSleepReason.POWER_BUTTON)
+ assertFalse(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
+ }
+
+ @Test
fun onCameraLaunchGestureDetected_maintainsAllOtherState() {
underTest.onStartedWakingUp(
PowerManager.WAKE_REASON_POWER_BUTTON,
@@ -211,8 +229,10 @@
underTest.onFinishedWakingUp()
underTest.onCameraLaunchGestureDetected()
- assertEquals(WakefulnessState.AWAKE, repository.wakefulness.value.internalWakefulnessState)
- assertEquals(WakeSleepReason.POWER_BUTTON, repository.wakefulness.value.lastWakeReason)
+ assertThat(repository.wakefulness.value.internalWakefulnessState)
+ .isEqualTo(WakefulnessState.AWAKE)
+ assertThat(repository.wakefulness.value.lastWakeReason)
+ .isEqualTo(WakeSleepReason.POWER_BUTTON)
assertTrue(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
}
@@ -221,21 +241,23 @@
underTest.onCameraLaunchGestureDetected()
// Ensure that the 'false' here does not clear the direct launch detection call earlier.
// This state should only be reset onStartedGoingToSleep.
- underTest.onFinishedGoingToSleep(/*powerButtonLaunchGestureTriggeredDuringSleep= */ false)
+ underTest.onFinishedGoingToSleep(/* powerButtonLaunchGestureTriggeredDuringSleep= */ false)
underTest.onStartedWakingUp(
PowerManager.WAKE_REASON_POWER_BUTTON,
/*powerButtonLaunchGestureTriggeredDuringSleep= */ false
)
underTest.onFinishedWakingUp()
- assertEquals(WakefulnessState.AWAKE, repository.wakefulness.value.internalWakefulnessState)
- assertEquals(WakeSleepReason.POWER_BUTTON, repository.wakefulness.value.lastWakeReason)
+ assertThat(repository.wakefulness.value.internalWakefulnessState)
+ .isEqualTo(WakefulnessState.AWAKE)
+ assertThat(repository.wakefulness.value.lastWakeReason)
+ .isEqualTo(WakeSleepReason.POWER_BUTTON)
assertTrue(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
}
@Test
fun cameraLaunchDetectedOnGoingToSleep_stillTrue_ifGestureNotDetectedOnWakingUp() {
- underTest.onFinishedGoingToSleep(/*powerButtonLaunchGestureTriggeredDuringSleep= */ true)
+ underTest.onFinishedGoingToSleep(/* powerButtonLaunchGestureTriggeredDuringSleep= */ true)
// Ensure that the 'false' here does not clear the direct launch detection call earlier.
// This state should only be reset onStartedGoingToSleep.
underTest.onStartedWakingUp(
@@ -244,12 +266,10 @@
)
underTest.onFinishedWakingUp()
- assertEquals(WakefulnessState.AWAKE, repository.wakefulness.value.internalWakefulnessState)
- assertEquals(WakeSleepReason.POWER_BUTTON, repository.wakefulness.value.lastWakeReason)
+ assertThat(repository.wakefulness.value.internalWakefulnessState)
+ .isEqualTo(WakefulnessState.AWAKE)
+ assertThat(repository.wakefulness.value.lastWakeReason)
+ .isEqualTo(WakeSleepReason.POWER_BUTTON)
assertTrue(repository.wakefulness.value.powerButtonLaunchGestureTriggered)
}
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
index 56b3679..42db96e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -23,8 +23,8 @@
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
-import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -42,6 +42,8 @@
@RunWith(AndroidJUnit4::class)
class GridConsistencyInteractorTest : SysuiTestCase() {
+ data object NoopGridLayoutType : GridLayoutType
+
private val kosmos =
testKosmos().apply {
defaultLargeTilesRepository =
@@ -54,6 +56,11 @@
TileSpec.create("largeD"),
)
}
+ gridConsistencyInteractorsMap =
+ mapOf(
+ Pair(NoopGridLayoutType, noopGridConsistencyInteractor),
+ Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)
+ )
}
private val underTest = with(kosmos) { gridConsistencyInteractor }
@@ -71,7 +78,7 @@
with(kosmos) {
testScope.runTest {
// Using the no-op grid consistency interactor
- gridLayoutTypeRepository.setLayout(PartitionedGridLayoutType)
+ gridLayoutTypeRepository.setLayout(NoopGridLayoutType)
// Setting an invalid layout with holes
// [ Large A ] [ sa ]
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt
deleted file mode 100644
index 55b7454..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayoutTest.kt
+++ /dev/null
@@ -1,123 +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.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.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
-import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class PartitionedGridLayoutTest : SysuiTestCase() {
- private val kosmos =
- testKosmos().apply {
- defaultLargeTilesRepository =
- object : DefaultLargeTilesRepository {
- override val defaultLargeTiles: Set<TileSpec> = setOf(TileSpec.create("large"))
- }
- }
-
- private val underTest = with(kosmos) { PartitionedGridLayout(partitionedGridViewModel) }
-
- @Test
- fun correctPagination_underOnePage_partitioned_sameRelativeOrder() =
- with(kosmos) {
- testScope.runTest {
- val rows = 3
- val columns = 4
-
- val tiles =
- listOf(
- largeTile(),
- smallTile(),
- smallTile(),
- largeTile(),
- largeTile(),
- smallTile()
- )
- val (smallTiles, largeTiles) =
- tiles.partition { iconTilesViewModel.isIconTile(it.spec) }
-
- // [L L] [L L]
- // [L L]
- // [S] [S] [S]
-
- val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns)
-
- Truth.assertThat(pages).hasSize(1)
- Truth.assertThat(pages[0]).isEqualTo(largeTiles + smallTiles)
- }
- }
-
- @Test
- fun correctPagination_twoPages_partitioned_sameRelativeOrder() =
- with(kosmos) {
- testScope.runTest {
- val rows = 3
- val columns = 4
-
- val tiles =
- listOf(
- largeTile(),
- smallTile(),
- smallTile(),
- largeTile(),
- smallTile(),
- smallTile(),
- largeTile(),
- smallTile(),
- smallTile(),
- )
- // --- Page 1 ---
- // [L L] [L L]
- // [L L]
- // [S] [S] [S] [S]
- // --- Page 2 ---
- // [S] [S]
-
- val (smallTiles, largeTiles) =
- tiles.partition { iconTilesViewModel.isIconTile(it.spec) }
-
- val pages = underTest.splitIntoPages(tiles, rows = rows, columns = columns)
-
- val expectedPage0 = largeTiles + smallTiles.take(4)
- val expectedPage1 = smallTiles.drop(4)
-
- Truth.assertThat(pages).hasSize(2)
- Truth.assertThat(pages[0]).isEqualTo(expectedPage0)
- Truth.assertThat(pages[1]).isEqualTo(expectedPage1)
- }
- }
-
- companion object {
- fun largeTile() = MockTileViewModel(TileSpec.create("large"))
-
- fun smallTile() = MockTileViewModel(TileSpec.create("small"))
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index 09dca25..69b8ee1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -22,11 +22,14 @@
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.settingslib.notification.data.repository.FakeZenModeRepository
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
@@ -40,51 +43,72 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class ModesTileDataInteractorTest : SysuiTestCase() {
- private val zenModeRepository = FakeZenModeRepository()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val dispatcher = kosmos.testDispatcher
+ private val zenModeRepository = kosmos.fakeZenModeRepository
- private val underTest = ModesTileDataInteractor(zenModeRepository)
+ private val underTest = ModesTileDataInteractor(zenModeRepository, dispatcher)
@EnableFlags(Flags.FLAG_MODES_UI)
@Test
- fun availableWhenFlagIsOn() = runTest {
- val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+ fun availableWhenFlagIsOn() =
+ testScope.runTest {
+ val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
- assertThat(availability).containsExactly(true)
- }
+ assertThat(availability).containsExactly(true)
+ }
@DisableFlags(Flags.FLAG_MODES_UI)
@Test
- fun unavailableWhenFlagIsOff() = runTest {
- val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+ fun unavailableWhenFlagIsOff() =
+ testScope.runTest {
+ val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
- assertThat(availability).containsExactly(false)
- }
+ assertThat(availability).containsExactly(false)
+ }
@EnableFlags(Flags.FLAG_MODES_UI)
@Test
- fun isActivatedWhenModesChange() = runTest {
- val dataList: List<ModesTileModel> by
- collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
- runCurrent()
- assertThat(dataList.map { it.isActivated }).containsExactly(false).inOrder()
+ fun isActivatedWhenModesChange() =
+ testScope.runTest {
+ val dataList: List<ModesTileModel> by
+ collectValues(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false).inOrder()
- // Add active mode
- zenModeRepository.addMode(id = "One", active = true)
- runCurrent()
- assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
+ // Add active mode
+ zenModeRepository.addMode(id = "One", active = true)
+ runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
+ assertThat(dataList.map { it.activeModes }.last()).containsExactly("Mode One")
- // Add another mode: state hasn't changed, so this shouldn't cause another emission
- zenModeRepository.addMode(id = "Two", active = true)
- runCurrent()
- assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
+ // Add an inactive mode: state hasn't changed, so this shouldn't cause another emission
+ zenModeRepository.addMode(id = "Two", active = false)
+ runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true).inOrder()
+ assertThat(dataList.map { it.activeModes }.last()).containsExactly("Mode One")
- // Remove a mode and disable the other
- zenModeRepository.removeMode("One")
- runCurrent()
- zenModeRepository.deactivateMode("Two")
- runCurrent()
- assertThat(dataList.map { it.isActivated }).containsExactly(false, true, false).inOrder()
- }
+ // Add another active mode
+ zenModeRepository.addMode(id = "Three", active = true)
+ runCurrent()
+ assertThat(dataList.map { it.isActivated }).containsExactly(false, true, true).inOrder()
+ assertThat(dataList.map { it.activeModes }.last())
+ .containsExactly("Mode One", "Mode Three")
+ .inOrder()
+
+ // Remove a mode and deactivate the other
+ zenModeRepository.removeMode("One")
+ runCurrent()
+ zenModeRepository.deactivateMode("Three")
+ runCurrent()
+ assertThat(dataList.map { it.isActivated })
+ .containsExactly(false, true, true, true, false)
+ .inOrder()
+ assertThat(dataList.map { it.activeModes }.last()).isEmpty()
+ }
private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
index a67e7c6..4b75649 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt
@@ -54,7 +54,11 @@
fun handleClick_active() = runTest {
val expandable = mock<Expandable>()
underTest.handleInput(
- QSTileInputTestKtx.click(data = ModesTileModel(true), expandable = expandable))
+ QSTileInputTestKtx.click(
+ data = ModesTileModel(true, listOf("DND")),
+ expandable = expandable
+ )
+ )
verify(mockDialogDelegate).showDialog(eq(expandable))
}
@@ -63,14 +67,18 @@
fun handleClick_inactive() = runTest {
val expandable = mock<Expandable>()
underTest.handleInput(
- QSTileInputTestKtx.click(data = ModesTileModel(false), expandable = expandable))
+ QSTileInputTestKtx.click(
+ data = ModesTileModel(false, emptyList()),
+ expandable = expandable
+ )
+ )
verify(mockDialogDelegate).showDialog(eq(expandable))
}
@Test
fun handleLongClick_active() = runTest {
- underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(true)))
+ underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(true, listOf("DND"))))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
@@ -79,7 +87,7 @@
@Test
fun handleLongClick_inactive() = runTest {
- underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(false)))
+ underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(false, emptyList())))
QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
index 3baf2f40..dd9711e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -54,21 +54,35 @@
@Test
fun inactiveState() {
- val model = ModesTileModel(isActivated = false)
+ val model = ModesTileModel(isActivated = false, activeModes = emptyList())
val state = underTest.map(config, model)
assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE)
assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_off)
+ assertThat(state.secondaryLabel).isEqualTo("No active modes")
}
@Test
- fun activeState() {
- val model = ModesTileModel(isActivated = true)
+ fun activeState_oneMode() {
+ val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"))
val state = underTest.map(config, model)
assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_on)
+ assertThat(state.secondaryLabel).isEqualTo("DND is active")
+ }
+
+ @Test
+ fun activeState_multipleModes() {
+ val model =
+ ModesTileModel(isActivated = true, activeModes = listOf("Mode 1", "Mode 2", "Mode 3"))
+
+ val state = underTest.map(config, model)
+
+ assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
+ assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_on)
+ assertThat(state.secondaryLabel).isEqualTo("3 modes are active")
}
}
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 9249621..3ca802e 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
@@ -93,7 +93,6 @@
sceneContainerStartable.start()
underTest =
QuickSettingsSceneViewModel(
- applicationScope = testScope.backgroundScope,
brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
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 39b3662..228d61a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -143,7 +143,6 @@
private val lockscreenSceneViewModel by lazy {
LockscreenSceneViewModel(
- applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
communalInteractor = communalInteractor,
touchHandling =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
index 0ab6a82..a4992e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt
@@ -53,7 +53,6 @@
fun setUp() {
underTest =
GoneSceneViewModel(
- applicationScope = testScope.backgroundScope,
shadeInteractor = kosmos.shadeInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 636d5a7..4a7b887 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -46,6 +46,7 @@
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -89,7 +90,7 @@
@RunWithLooper(setAsMainLooper = true)
@SmallTest
public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
- @Mock private WindowManager mWindowManager;
+ @Mock private ViewCaptureAwareWindowManager mWindowManager;
@Mock private DozeParameters mDozeParameters;
@Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy(
new NotificationShadeWindowView(mContext, null));
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 da22c6d..343b6bd 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
@@ -25,6 +25,7 @@
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activatable.activateIn
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
@@ -59,6 +60,7 @@
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
@@ -78,6 +80,11 @@
private val underTest: ShadeSceneViewModel by lazy { kosmos.shadeSceneViewModel }
+ @Before
+ fun setUp() {
+ underTest.activateIn(testScope)
+ }
+
@Test
fun upTransitionSceneKey_deviceLocked_lockScreen() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
index 8810ade..7b87aeb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
@@ -20,7 +20,6 @@
import android.app.Notification
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_LOW
-import android.os.UserHandle
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -475,20 +474,6 @@
val collectionListener: NotifCollectionListener =
argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
-
- var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
- get() =
- fakeSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- UserHandle.USER_CURRENT,
- ) == 1
- set(value) {
- fakeSettings.putIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- if (value) 1 else 2,
- UserHandle.USER_CURRENT,
- )
- }
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 7e9f437..3fd9c21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -18,21 +18,23 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
-import android.os.UserHandle
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
+import com.android.systemui.dump.dumpManager
import com.android.systemui.flags.andSceneContainer
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.scene.data.repository.Idle
import com.android.systemui.scene.data.repository.setTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -43,12 +45,15 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.lockScreenShowOnlyUnseenNotificationsSetting
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
@@ -73,13 +78,23 @@
@RunWith(ParameterizedAndroidJunit4::class)
class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos =
+ testKosmos().apply {
+ testDispatcher = UnconfinedTestDispatcher()
+ statusBarStateController = mock()
+ fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+ }
- private val headsUpManager: HeadsUpManager = mock()
- private val keyguardRepository = FakeKeyguardRepository()
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardRepository
+ get() = kosmos.fakeKeyguardRepository
+
+ private val keyguardTransitionRepository
+ get() = kosmos.fakeKeyguardTransitionRepository
+
+ private val statusBarStateController
+ get() = kosmos.statusBarStateController
+
private val notifPipeline: NotifPipeline = mock()
- private val statusBarStateController: StatusBarStateController = mock()
init {
mSetFlagsRule.setFlagsParameterization(flags)
@@ -253,7 +268,7 @@
collectionListener.onEntryAdded(fakeEntry)
// GIVEN: The setting for filtering unseen notifications is disabled
- showOnlyUnseenNotifsOnKeyguardSetting = false
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = false
// GIVEN: The pipeline has registered the unseen filter for invalidation
val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock()
@@ -267,7 +282,7 @@
assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
// WHEN: The secure setting is changed
- showOnlyUnseenNotifsOnKeyguardSetting = true
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = true
// THEN: The pipeline is invalidated
verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), any())
@@ -608,35 +623,25 @@
private fun runKeyguardCoordinatorTest(
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
) {
- val testDispatcher = UnconfinedTestDispatcher()
- val testScope = TestScope(testDispatcher)
- val fakeSettings =
- FakeSettings().apply {
- putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
- }
- val seenNotificationsInteractor =
- SeenNotificationsInteractor(ActiveNotificationListRepository())
val keyguardCoordinator =
OriginalUnseenKeyguardCoordinator(
- testDispatcher,
- mock<DumpManager>(),
- headsUpManager,
- keyguardRepository,
- kosmos.keyguardTransitionInteractor,
- KeyguardCoordinatorLogger(logcatLogBuffer()),
- testScope.backgroundScope,
- fakeSettings,
- seenNotificationsInteractor,
- statusBarStateController,
+ dumpManager = kosmos.dumpManager,
+ headsUpManager = kosmos.headsUpManager,
+ keyguardRepository = kosmos.keyguardRepository,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ logger = KeyguardCoordinatorLogger(logcatLogBuffer()),
+ scope = kosmos.testScope.backgroundScope,
+ seenNotificationsInteractor = kosmos.seenNotificationsInteractor,
+ statusBarStateController = kosmos.statusBarStateController,
sceneInteractor = kosmos.sceneInteractor,
)
keyguardCoordinator.attach(notifPipeline)
- testScope.runTest {
+ kosmos.testScope.runTest {
KeyguardCoordinatorTestScope(
keyguardCoordinator,
- testScope,
- seenNotificationsInteractor,
- fakeSettings,
+ kosmos.testScope,
+ kosmos.seenNotificationsInteractor,
+ kosmos.fakeSettings,
)
.testBlock()
}
@@ -658,21 +663,8 @@
argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue
val onHeadsUpChangedListener: OnHeadsUpChangedListener
- get() = argumentCaptor { verify(headsUpManager).addListener(capture()) }.lastValue
-
- var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
get() =
- fakeSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- UserHandle.USER_CURRENT,
- ) == 1
- set(value) {
- fakeSettings.putIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- if (value) 1 else 2,
- UserHandle.USER_CURRENT,
- )
- }
+ argumentCaptor { verify(kosmos.headsUpManager).addListener(capture()) }.lastValue
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
new file mode 100644
index 0000000..2159b86
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.statusbar.notification.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+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.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class SeenNotificationsInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val underTest
+ get() = kosmos.seenNotificationsInteractor
+
+ @Test
+ fun testNoFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(false)
+
+ assertThat(hasFilteredOutSeenNotifications).isFalse()
+ }
+
+ @Test
+ fun testHasFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(true)
+
+ assertThat(hasFilteredOutSeenNotifications).isTrue()
+ }
+
+ @Test
+ @EnableFlags(NotificationMinimalismPrototype.FLAG_NAME)
+ fun topOngoingAndUnseenNotification() = runTest {
+ val entry1 = NotificationEntryBuilder().setTag("entry1").build()
+ val entry2 = NotificationEntryBuilder().setTag("entry2").build()
+
+ underTest.setTopOngoingNotification(null)
+ underTest.setTopUnseenNotification(null)
+
+ assertThat(underTest.isTopOngoingNotification(entry1)).isFalse()
+ assertThat(underTest.isTopOngoingNotification(entry2)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry1)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry2)).isFalse()
+
+ underTest.setTopOngoingNotification(entry1)
+ underTest.setTopUnseenNotification(entry2)
+
+ assertThat(underTest.isTopOngoingNotification(entry1)).isTrue()
+ assertThat(underTest.isTopOngoingNotification(entry2)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry1)).isFalse()
+ assertThat(underTest.isTopUnseenNotification(entry2)).isTrue()
+ }
+
+ fun testShowOnlyUnseenNotifsOnKeyguardSetting() = runTest {
+ val settingEnabled by
+ collectLastValue(underTest.isLockScreenShowOnlyUnseenNotificationsEnabled())
+
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = false
+ testScheduler.runCurrent()
+ assertThat(settingEnabled).isFalse()
+
+ kosmos.lockScreenShowOnlyUnseenNotificationsSetting = true
+ testScheduler.runCurrent()
+ assertThat(settingEnabled).isTrue()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 6f09931..6f1bc7e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -60,7 +60,6 @@
import com.android.systemui.res.R
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.shade.shadeTestUtil
-import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -590,6 +589,43 @@
}
@Test
+ @DisableSceneContainer
+ fun boundsDoNotChangeWhileLockscreenToAodTransitionIsActive() =
+ testScope.runTest {
+ val bounds by collectLastValue(underTest.bounds)
+
+ // Start on lockscreen
+ showLockscreen()
+
+ keyguardInteractor.setNotificationContainerBounds(
+ NotificationContainerBounds(top = 1f, bottom = 1f)
+ )
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 1f))
+
+ // Begin transition to AOD
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(LOCKSCREEN, AOD, 0f, TransitionState.STARTED)
+ )
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(LOCKSCREEN, AOD, 0.5f, TransitionState.RUNNING)
+ )
+
+ // Attempt to update bounds
+ keyguardInteractor.setNotificationContainerBounds(
+ NotificationContainerBounds(top = 5f, bottom = 5f)
+ )
+ // Bounds should not have moved
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 1f, bottom = 1f))
+
+ // Transition is over, now move
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(LOCKSCREEN, AOD, 1f, TransitionState.FINISHED)
+ )
+ assertThat(bounds).isEqualTo(NotificationContainerBounds(top = 5f, bottom = 5f))
+ }
+
+ @Test
@DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
@DisableSceneContainer
fun boundsOnLockscreenInSplitShade_refactorFlagOff_usesLargeHeaderResource() =
@@ -820,54 +856,6 @@
@Test
@DisableSceneContainer
- fun updateBounds_fromKeyguardRoot() =
- testScope.runTest {
- val startProgress = 0f
- val startStep = TransitionStep(LOCKSCREEN, AOD, startProgress, TransitionState.STARTED)
- val boundsChangingProgress = 0.2f
- val boundsChangingStep =
- TransitionStep(LOCKSCREEN, AOD, boundsChangingProgress, TransitionState.RUNNING)
- val boundsInterpolatingProgress = 0.6f
- val boundsInterpolatingStep =
- TransitionStep(
- LOCKSCREEN,
- AOD,
- boundsInterpolatingProgress,
- TransitionState.RUNNING
- )
- val finishProgress = 1.0f
- val finishStep =
- TransitionStep(LOCKSCREEN, AOD, finishProgress, TransitionState.FINISHED)
-
- val bounds by collectLastValue(underTest.bounds)
- val top = 123f
- val bottom = 456f
-
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(startStep)
- runCurrent()
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsChangingStep)
- runCurrent()
- keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
-
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsInterpolatingStep)
- runCurrent()
- val adjustedProgress =
- (boundsInterpolatingProgress - boundsChangingProgress) /
- (1 - boundsChangingProgress)
- val interpolatedTop = interpolate(0f, top, adjustedProgress)
- val interpolatedBottom = interpolate(0f, bottom, adjustedProgress)
- assertThat(bounds)
- .isEqualTo(
- NotificationContainerBounds(top = interpolatedTop, bottom = interpolatedBottom)
- )
-
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
- runCurrent()
- assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
- }
-
- @Test
- @DisableSceneContainer
fun updateBounds_fromGone_withoutTransitions() =
testScope.runTest {
// Start step is already at 1.0
@@ -878,9 +866,9 @@
val top = 123f
val bottom = 456f
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(runningStep)
+ keyguardTransitionRepository.sendTransitionStep(runningStep)
runCurrent()
- kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
+ keyguardTransitionRepository.sendTransitionStep(finishStep)
runCurrent()
keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 8f9da3b..9a862fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -60,6 +60,7 @@
// For creating TestableHeadsUpManager
@Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
private val mUiEventLoggerFake = UiEventLoggerFake()
+ @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
@Mock private lateinit var mBgHandler: Handler
@@ -82,7 +83,8 @@
// Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
// declaration, where mocks are null
- mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler)
+ mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake,
+ mHeadsUpManagerLogger, mBgHandler)
testableHeadsUpManager =
TestableHeadsUpManager(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index df07b44..9005ae3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -81,6 +81,7 @@
static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
+
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@Mock private Handler mBgHandler;
@Mock private DumpManager dumpManager;
@@ -149,7 +150,8 @@
@Override
public void SysuiSetup() throws Exception {
super.SysuiSetup();
- mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mBgHandler);
+ mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mLogger,
+ mBgHandler);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index b91bde4..7a6838a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -179,7 +179,8 @@
mContext
.getOrCreateTestableResources()
.addOverride(R.integer.ambient_notification_extension_time, 500)
- mAvalancheController = AvalancheController(dumpManager, mUiEventLogger, mBgHandler)
+ mAvalancheController = AvalancheController(dumpManager, mUiEventLogger,
+ mHeadsUpManagerLogger, mBgHandler)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 349b62e..62161bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -54,13 +55,20 @@
ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate)
@Test
- fun tiles_filtersOutDisabledModes() =
+ fun tiles_filtersOutUserDisabledModes() =
testScope.runTest {
val tiles by collectLastValue(underTest.tiles)
repository.addModes(
listOf(
- TestModeBuilder().setName("Disabled").setEnabled(false).build(),
+ TestModeBuilder()
+ .setName("Disabled by user")
+ .setEnabled(false, /* byUser= */ true)
+ .build(),
+ TestModeBuilder()
+ .setName("Disabled by other")
+ .setEnabled(false, /* byUser= */ false)
+ .build(),
TestModeBuilder.MANUAL_DND,
TestModeBuilder()
.setName("Enabled")
@@ -69,20 +77,25 @@
.build(),
TestModeBuilder()
.setName("Disabled with manual")
- .setEnabled(false)
+ .setEnabled(false, /* byUser= */ true)
.setManualInvocationAllowed(true)
.build(),
)
)
runCurrent()
- assertThat(tiles?.size).isEqualTo(2)
+ assertThat(tiles?.size).isEqualTo(3)
with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Disabled by other")
+ assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.enabled).isEqualTo(false)
+ }
+ with(tiles?.elementAt(1)!!) {
assertThat(this.text).isEqualTo("Manual DND")
assertThat(this.subtext).isEqualTo("On")
assertThat(this.enabled).isEqualTo(true)
}
- with(tiles?.elementAt(1)!!) {
+ with(tiles?.elementAt(2)!!) {
assertThat(this.text).isEqualTo("Enabled")
assertThat(this.subtext).isEqualTo("Off")
assertThat(this.enabled).isEqualTo(false)
@@ -140,6 +153,117 @@
}
@Test
+ fun tiles_stableWhileCollecting() =
+ testScope.runTest {
+ val job = Job()
+ val tiles by collectLastValue(underTest.tiles, context = job)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setName("Active without manual")
+ .setActive(true)
+ .setManualInvocationAllowed(false)
+ .build(),
+ TestModeBuilder()
+ .setName("Active with manual")
+ .setActive(true)
+ .setManualInvocationAllowed(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Inactive with manual")
+ .setActive(false)
+ .setManualInvocationAllowed(true)
+ .build(),
+ TestModeBuilder()
+ .setName("Inactive without manual")
+ .setActive(false)
+ .setManualInvocationAllowed(false)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(3)
+
+ // Check that tile is initially present
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Active without manual")
+ assertThat(this.subtext).isEqualTo("On")
+ assertThat(this.enabled).isEqualTo(true)
+
+ // Click tile to toggle it
+ this.onClick()
+ runCurrent()
+ }
+ // Check that tile is still present at the same location, but turned off
+ assertThat(tiles?.size).isEqualTo(3)
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Active without manual")
+ assertThat(this.subtext).isEqualTo("Manage in settings")
+ assertThat(this.enabled).isEqualTo(false)
+ }
+
+ // Stop collecting, then start again
+ job.cancel()
+ val tiles2 by collectLastValue(underTest.tiles)
+ runCurrent()
+
+ // Check that tile is now gone
+ assertThat(tiles2?.size).isEqualTo(2)
+ assertThat(tiles2?.elementAt(0)!!.text).isEqualTo("Active with manual")
+ assertThat(tiles2?.elementAt(1)!!.text).isEqualTo("Inactive with manual")
+ }
+
+ @Test
+ fun tiles_filtersOutRemovedModes() =
+ testScope.runTest {
+ val job = Job()
+ val tiles by collectLastValue(underTest.tiles, context = job)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setId("A")
+ .setName("Active without manual")
+ .setActive(true)
+ .setManualInvocationAllowed(false)
+ .build(),
+ TestModeBuilder()
+ .setId("B")
+ .setName("Active with manual")
+ .setActive(true)
+ .setManualInvocationAllowed(true)
+ .build(),
+ TestModeBuilder()
+ .setId("C")
+ .setName("Inactive with manual")
+ .setActive(false)
+ .setManualInvocationAllowed(true)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(3)
+
+ repository.removeMode("A")
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(2)
+
+ repository.removeMode("B")
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(1)
+
+ repository.removeMode("C")
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(0)
+ }
+
+ @Test
fun onClick_togglesTileState() =
testScope.runTest {
val tiles by collectLastValue(underTest.tiles)
@@ -173,6 +297,91 @@
}
@Test
+ fun onClick_noManualActivation() =
+ testScope.runTest {
+ val job = Job()
+ val tiles by collectLastValue(underTest.tiles, context = job)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setName("Active without manual")
+ .setActive(true)
+ .setManualInvocationAllowed(false)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(1)
+
+ // Click tile to toggle it off
+ tiles?.elementAt(0)!!.onClick()
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(1)
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Active without manual")
+ assertThat(this.subtext).isEqualTo("Manage in settings")
+ assertThat(this.enabled).isEqualTo(false)
+
+ // Press the tile again
+ this.onClick()
+ runCurrent()
+ }
+
+ // Check that nothing happened
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Active without manual")
+ assertThat(this.subtext).isEqualTo("Manage in settings")
+ assertThat(this.enabled).isEqualTo(false)
+ }
+ }
+
+ @Test
+ fun onClick_setUp() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tiles)
+
+ repository.addModes(
+ listOf(
+ TestModeBuilder()
+ .setId("ID")
+ .setName("Disabled by other")
+ .setEnabled(false, /* byUser= */ false)
+ .build(),
+ )
+ )
+ runCurrent()
+
+ assertThat(tiles?.size).isEqualTo(1)
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Disabled by other")
+ assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.enabled).isEqualTo(false)
+
+ // Click the tile
+ this.onClick()
+ runCurrent()
+ }
+
+ // Check that it launched the correct intent
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(mockDialogDelegate).launchFromDialog(intentCaptor.capture())
+ val intent = intentCaptor.lastValue
+ assertThat(intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+ assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
+ .isEqualTo("ID")
+
+ // Check that nothing happened to the tile
+ with(tiles?.elementAt(0)!!) {
+ assertThat(this.text).isEqualTo("Disabled by other")
+ assertThat(this.subtext).isEqualTo("Set up")
+ assertThat(this.enabled).isEqualTo(false)
+ }
+ }
+
+ @Test
fun onLongClick_launchesIntent() =
testScope.runTest {
val tiles by collectLastValue(underTest.tiles)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
index 142631e6..a1fcfcd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
@@ -16,8 +16,10 @@
package com.android.systemui.volume.domain.interactor
+import android.media.AudioManager.STREAM_MUSIC
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -40,17 +42,57 @@
@Before
fun setUp() {
- with(kosmos) { underTest = audioSharingInteractor }
+ with(kosmos) {
+ with(audioSharingRepository) { setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME)) }
+ underTest = audioSharingInteractor
+ }
}
@Test
- fun volumeChanges_returnVolume() {
+ fun handlePrimaryGroupChange_nullVolume() {
with(kosmos) {
testScope.runTest {
- with(audioSharingRepository) {
- setSecondaryGroupId(TEST_GROUP_ID)
- setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
- }
+ with(audioSharingRepository) { setPrimaryGroupId(TEST_GROUP_ID_INVALID) }
+ val preMusicStream by
+ collectLastValue(
+ audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC))
+ )
+ val preVolume = preMusicStream?.volume
+ runCurrent()
+ underTest.handlePrimaryGroupChange()
+ val musicStream by
+ collectLastValue(
+ audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC))
+ )
+ runCurrent()
+
+ Truth.assertThat(musicStream?.volume).isEqualTo(preVolume)
+ }
+ }
+ }
+
+ @Test
+ fun handlePrimaryGroupChange_setStreamVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) { setPrimaryGroupId(TEST_GROUP_ID) }
+ underTest.handlePrimaryGroupChange()
+ val musicStream by
+ collectLastValue(
+ audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC))
+ )
+ runCurrent()
+
+ Truth.assertThat(musicStream?.volume).isEqualTo(TEST_MUSIC_VOLUME)
+ }
+ }
+ }
+
+ @Test
+ fun secondaryGroupVolumeChanges_returnVolume() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) { setSecondaryGroupId(TEST_GROUP_ID) }
val volume by collectLastValue(underTest.volume)
runCurrent()
@@ -60,13 +102,10 @@
}
@Test
- fun volumeChanges_returnNull() {
+ fun secondaryGroupVolumeChanges_returnNull() {
with(kosmos) {
testScope.runTest {
- with(audioSharingRepository) {
- setSecondaryGroupId(TEST_GROUP_ID_INVALID)
- setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME))
- }
+ with(audioSharingRepository) { setSecondaryGroupId(TEST_GROUP_ID_INVALID) }
val volume by collectLastValue(underTest.volume)
runCurrent()
@@ -76,7 +115,7 @@
}
@Test
- fun volumeChanges_returnDefaultVolume() {
+ fun secondaryGroupVolumeChanges_returnDefaultVolume() {
with(kosmos) {
testScope.runTest {
with(audioSharingRepository) {
@@ -94,7 +133,8 @@
private companion object {
const val TEST_GROUP_ID = 1
const val TEST_GROUP_ID_INVALID = -1
- const val TEST_VOLUME = 10
+ const val TEST_MUSIC_VOLUME = 10
+ const val TEST_VOLUME = 255
const val TEST_VOLUME_DEFAULT = 20
}
}
diff --git a/packages/SystemUI/res/layout/app_clips_backlinks_drop_down_entry.xml b/packages/SystemUI/res/layout/app_clips_backlinks_drop_down_entry.xml
new file mode 100644
index 0000000..7eab340
--- /dev/null
+++ b/packages/SystemUI/res/layout/app_clips_backlinks_drop_down_entry.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.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:drawablePadding="4dp"
+ android:ellipsize="end"
+ android:gravity="center_vertical"
+ android:paddingHorizontal="8dp"
+ android:textColor="?android:textColorSecondary" />
diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
index dc56097..154397d 100644
--- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml
@@ -23,7 +23,8 @@
android:layout_gravity="center_vertical|start"
android:layout_marginStart="5dp"
>
- <!-- TODO(b/354930838): Fix these paddings for the new screen chips. -->
+ <!-- TODO(b/332662551): Update this content description when this supports more than just
+ phone calls. -->
<com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
android:id="@+id/ongoing_activity_chip_background"
android:layout_width="wrap_content"
@@ -31,18 +32,18 @@
android:layout_gravity="center_vertical"
android:gravity="center"
android:background="@drawable/ongoing_activity_chip_bg"
- android:paddingStart="@dimen/ongoing_activity_chip_side_padding_with_notif_icon"
- android:paddingEnd="@dimen/ongoing_activity_chip_side_padding_with_notif_icon"
+ android:paddingStart="@dimen/ongoing_activity_chip_side_padding"
+ android:paddingEnd="@dimen/ongoing_activity_chip_side_padding"
android:minWidth="@dimen/min_clickable_item_size"
>
<ImageView
android:src="@*android:drawable/ic_phone"
android:id="@+id/ongoing_activity_chip_icon"
+ android:contentDescription="@string/ongoing_phone_call_content_description"
android:layout_width="@dimen/ongoing_activity_chip_icon_size"
android:layout_height="@dimen/ongoing_activity_chip_icon_size"
android:tint="?android:attr/colorPrimary"
- android:visibility="gone"
/>
<!-- Only one of [ongoing_activity_chip_time, ongoing_activity_chip_text] will ever
@@ -53,7 +54,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:gravity="center|start"
- android:paddingEnd="@dimen/ongoing_activity_chip_text_end_padding_with_notif_icon"
+ android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding"
android:textAppearance="@android:style/TextAppearance.Material.Small"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:textColor="?android:attr/colorPrimary"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 30f23bf..c29c236 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -104,7 +104,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- internet,bt,flashlight,dnd,modes,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices
+ internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e459348..eda7bb0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1730,15 +1730,9 @@
<!-- Ongoing activity chip -->
<dimen name="ongoing_activity_chip_side_padding">12dp</dimen>
- <!-- The start padding to use on the activity chip if the icon comes from the notification's smallIcon field (the small notif icons embed their own padding, so the chip itself can have less padding) -->
- <dimen name="ongoing_activity_chip_side_padding_with_notif_icon">6dp</dimen>
<dimen name="ongoing_activity_chip_icon_size">16dp</dimen>
- <!-- The size to use for the icon in the ongoing activity chip if the icon comes from the notification's smallIcon field -->
- <dimen name="ongoing_activity_chip_notif_icon_size">22dp</dimen>
-
<!-- The padding between the icon and the text. -->
<dimen name="ongoing_activity_chip_icon_text_padding">4dp</dimen>
- <dimen name="ongoing_activity_chip_text_end_padding_with_notif_icon">6dp</dimen>
<dimen name="ongoing_activity_chip_corner_radius">28dp</dimen>
<!-- Status bar user chip -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8146cc5..089db2d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1103,6 +1103,20 @@
<!-- Priority modes: label for an inactive mode [CHAR LIMIT=35] -->
<string name="zen_mode_off">Off</string>
+ <!-- Priority modes: label for a mode that needs to be set up [CHAR LIMIT=35] -->
+ <string name="zen_mode_set_up">Set up</string>
+
+ <!-- Priority modes: label for a mode that cannot be manually turned on [CHAR LIMIT=35] -->
+ <string name="zen_mode_no_manual_invocation">Manage in settings</string>
+
+ <string name="zen_mode_active_modes">
+ {count, plural,
+ =0 {No active modes}
+ =1 {{mode} is active}
+ other {# modes are active}
+ }
+ </string>
+
<!-- Zen mode: Priority only introduction message on first use -->
<string name="zen_priority_introduction">You won\'t be disturbed by sounds and vibrations, except from alarms, reminders, events, and callers you specify. You\'ll still hear anything you choose to play including music, videos, and games.</string>
@@ -3208,9 +3222,6 @@
<!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
<string name="mobile_data_settings_title">Mobile data</string>
- <!-- Provider Model: Summary text separator for preferences including a short description
- (eg. "Connected / 5G"). [CHAR LIMIT=50] -->
- <string name="preference_summary_default_combination"><xliff:g id="state" example="Connected">%1$s</xliff:g> / <xliff:g id="networkMode" example="LTE">%2$s</xliff:g></string>
<!-- Provider Model:
Summary indicating that a SIM has an active mobile data connection [CHAR LIMIT=50] -->
<string name="mobile_data_connection_active">Connected</string>
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index c702927..ad09b46 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -85,16 +85,6 @@
<item>On</item>
</string-array>
- <!-- State names for modes (Priority modes) tile: unavailable, off, on.
- This subtitle is shown when the tile is in that particular state but does not set its own
- subtitle, so some of these may never appear on screen. They should still be translated as
- if they could appear. [CHAR LIMIT=32] -->
- <string-array name="tile_states_modes">
- <item>Unavailable</item>
- <item>Off</item>
- <item>On</item>
- </string-array>
-
<!-- State names for flashlight tile: unavailable, off, on.
This subtitle is shown when the tile is in that particular state but does not set its own
subtitle, so some of these may never appear on screen. They should still be translated as
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 0f11717..1342dd0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -287,7 +287,7 @@
/**
* Helper used to receive device state info from {@link DeviceStateManager}.
*/
- static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
+ public static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
@Nullable
private final DisplayAddress.Physical mRearDisplayPhysicalAddress;
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardQuickAffordancesLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardQuickAffordancesLogger.kt
new file mode 100644
index 0000000..c11cf55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardQuickAffordancesLogger.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.keyguard.logging
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.KeyguardQuickAffordancesLog
+import javax.inject.Inject
+
+class KeyguardQuickAffordancesLogger
+@Inject
+constructor(
+ @KeyguardQuickAffordancesLog val buffer: LogBuffer,
+) {
+ fun logQuickAffordanceTapped(configKey: String?) {
+ val (slotId, affordanceId) = configKey?.decode() ?: ("" to "")
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = affordanceId
+ str2 = slotId
+ },
+ { "QuickAffordance tapped with id: $str1, in slot: $str2" }
+ )
+ }
+
+ fun logQuickAffordanceTriggered(slotId: String, affordanceId: String) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = affordanceId
+ str2 = slotId
+ },
+ { "QuickAffordance triggered with id: $str1, in slot: $str2" }
+ )
+ }
+
+ fun logQuickAffordanceSelected(slotId: String, affordanceId: String) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = affordanceId
+ str2 = slotId
+ },
+ { "QuickAffordance selected with id: $str1, in slot: $str2" }
+ )
+ }
+
+ private fun String.decode(): Pair<String, String> {
+ val splitUp = this.split(DELIMITER)
+ return Pair(splitUp[0], splitUp[1])
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordancesLogger"
+ private const val DELIMITER = "::"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt b/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt
new file mode 100644
index 0000000..dc2d931
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/activatable/Activatable.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.activatable
+
+/** Defines interface for classes that can be activated to do coroutine work. */
+interface Activatable {
+
+ /**
+ * Activates this object.
+ *
+ * Serves as an entrypoint to kick off coroutine work that the object requires in order to keep
+ * its state fresh and/or perform side-effects.
+ *
+ * The method suspends and doesn't return until all work required by the object is finished. In
+ * most cases, it's expected for the work to remain ongoing forever so this method will forever
+ * suspend its caller until the coroutine that called it is canceled.
+ *
+ * Implementations could follow this pattern:
+ * ```kotlin
+ * override suspend fun activate() {
+ * coroutineScope {
+ * launch { ... }
+ * launch { ... }
+ * launch { ... }
+ * }
+ * }
+ * ```
+ *
+ * **Must be invoked** by the owner of the object when the object is to become active.
+ * Similarly, the work must be canceled by the owner when the objects is to be deactivated.
+ *
+ * One way to have a parent call this would be by using a `LaunchedEffect` in Compose:
+ * ```kotlin
+ * @Composable
+ * fun MyUi(activatable: Activatable) {
+ * LaunchedEffect(activatable) {
+ * activatable.activate()
+ * }
+ * }
+ * ```
+ */
+ suspend fun activate()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
deleted file mode 100644
index 636bc5b..0000000
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
+++ /dev/null
@@ -1,395 +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.ambient.touch;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.InputEvent;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.Flags;
-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;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.wm.shell.animation.FlingAnimationUtils;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
- */
-public class BouncerSwipeTouchHandler implements TouchHandler {
- /**
- * An interface for creating ValueAnimators.
- */
- public interface ValueAnimatorCreator {
- /**
- * Creates {@link ValueAnimator}.
- */
- ValueAnimator create(float start, float finish);
- }
-
- /**
- * An interface for obtaining VelocityTrackers.
- */
- public interface VelocityTrackerFactory {
- /**
- * Obtains {@link VelocityTracker}.
- */
- VelocityTracker obtain();
- }
-
- public static final float FLING_PERCENTAGE_THRESHOLD = 0.5f;
-
- private static final String TAG = "BouncerSwipeTouchHandler";
- private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final LockPatternUtils mLockPatternUtils;
- private final UserTracker mUserTracker;
- private final float mBouncerZoneScreenPercentage;
- private final float mMinBouncerZoneScreenPercentage;
-
- private final ScrimManager mScrimManager;
- private ScrimController mCurrentScrimController;
- private float mCurrentExpansion;
- private final Optional<CentralSurfaces> mCentralSurfaces;
-
- private VelocityTracker mVelocityTracker;
-
- private final FlingAnimationUtils mFlingAnimationUtils;
- private final FlingAnimationUtils mFlingAnimationUtilsClosing;
-
- private Boolean mCapture;
- private Boolean mExpanded;
-
- private TouchSession mTouchSession;
-
- private final ValueAnimatorCreator mValueAnimatorCreator;
-
- private final VelocityTrackerFactory mVelocityTrackerFactory;
-
- private final UiEventLogger mUiEventLogger;
-
- private final ActivityStarter mActivityStarter;
-
- private final ScrimManager.Callback mScrimManagerCallback = new ScrimManager.Callback() {
- @Override
- public void onScrimControllerChanged(ScrimController controller) {
- if (mCurrentScrimController != null) {
- mCurrentScrimController.reset();
- }
-
- mCurrentScrimController = controller;
- }
- };
-
- private final GestureDetector.OnGestureListener mOnGestureListener =
- new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
- float distanceY) {
- if (mCapture == null) {
- if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
- mCapture = Math.abs(distanceY) > Math.abs(distanceX)
- && distanceY > 0;
- } else {
- // If the user scrolling favors a vertical direction, begin capturing
- // scrolls.
- mCapture = Math.abs(distanceY) > Math.abs(distanceX);
- }
- if (mCapture) {
- // reset expanding
- mExpanded = false;
- // Since the user is dragging the bouncer up, set scrimmed to false.
- mCurrentScrimController.show();
- }
- }
-
- if (!mCapture) {
- return false;
- }
-
- // Don't set expansion for downward scroll.
- if (e1.getY() < e2.getY()) {
- return true;
- }
-
- if (!mCentralSurfaces.isPresent()) {
- return true;
- }
-
- // 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())) {
- mActivityStarter.executeRunnableDismissingKeyguard(
- () -> mCentralSurfaces.get().awakenDreams(),
- /* cancelAction= */ null,
- /* dismissShade= */ true,
- /* afterKeyguardGone= */ true,
- /* deferred= */ false);
- return true;
- }
-
- // For consistency, we adopt the expansion definition found in the
- // PanelViewController. In this case, expansion refers to the view above the
- // 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 screenTravelPercentage = Math.abs(e1.getY() - e2.getY())
- / mTouchSession.getBounds().height();
- setPanelExpansion(1 - screenTravelPercentage);
- return true;
- }
- };
-
- private void setPanelExpansion(float expansion) {
- mCurrentExpansion = expansion;
- ShadeExpansionChangeEvent event =
- new ShadeExpansionChangeEvent(
- /* fraction= */ mCurrentExpansion,
- /* expanded= */ mExpanded,
- /* tracking= */ true);
- mCurrentScrimController.expand(event);
- }
-
-
- @VisibleForTesting
- public enum DreamEvent implements UiEventLogger.UiEventEnum {
- @UiEvent(doc = "The screensaver has been swiped up.")
- DREAM_SWIPED(988),
-
- @UiEvent(doc = "The bouncer has become fully visible over dream.")
- DREAM_BOUNCER_FULLY_VISIBLE(1056);
-
- private final int mId;
-
- DreamEvent(int id) {
- mId = id;
- }
-
- @Override
- public int getId() {
- return mId;
- }
- }
-
- @Inject
- public BouncerSwipeTouchHandler(
- ScrimManager scrimManager,
- Optional<CentralSurfaces> centralSurfaces,
- NotificationShadeWindowController notificationShadeWindowController,
- ValueAnimatorCreator valueAnimatorCreator,
- VelocityTrackerFactory velocityTrackerFactory,
- LockPatternUtils lockPatternUtils,
- UserTracker userTracker,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
- FlingAnimationUtils flingAnimationUtils,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
- FlingAnimationUtils flingAnimationUtilsClosing,
- @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
- @Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
- UiEventLogger uiEventLogger,
- ActivityStarter activityStarter) {
- mCentralSurfaces = centralSurfaces;
- mScrimManager = scrimManager;
- mNotificationShadeWindowController = notificationShadeWindowController;
- mLockPatternUtils = lockPatternUtils;
- mUserTracker = userTracker;
- mBouncerZoneScreenPercentage = swipeRegionPercentage;
- mMinBouncerZoneScreenPercentage = minRegionPercentage;
- mFlingAnimationUtils = flingAnimationUtils;
- mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
- mValueAnimatorCreator = valueAnimatorCreator;
- mVelocityTrackerFactory = velocityTrackerFactory;
- mUiEventLogger = uiEventLogger;
- mActivityStarter = activityStarter;
- }
-
- @Override
- public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
- final int width = bounds.width();
- final int height = bounds.height();
- final int minAllowableBottom = Math.round(height * (1 - mMinBouncerZoneScreenPercentage));
-
- final Rect normalRegion = new Rect(0,
- Math.round(height * (1 - mBouncerZoneScreenPercentage)),
- width, height);
-
- if (exclusionRect != null) {
- int lowestBottom = Math.min(Math.max(0, exclusionRect.bottom), minAllowableBottom);
- normalRegion.top = Math.max(normalRegion.top, lowestBottom);
- }
- region.union(normalRegion);
- }
-
-
- @Override
- public void onSessionStart(TouchSession session) {
- mVelocityTracker = mVelocityTrackerFactory.obtain();
- mTouchSession = session;
- mVelocityTracker.clear();
-
- if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
- mNotificationShadeWindowController.setForcePluginOpen(true, this);
- }
-
- mScrimManager.addCallback(mScrimManagerCallback);
- mCurrentScrimController = mScrimManager.getCurrentController();
-
- session.registerCallback(() -> {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- mScrimManager.removeCallback(mScrimManagerCallback);
- mCapture = null;
- mTouchSession = null;
-
- if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
- mNotificationShadeWindowController.setForcePluginOpen(false, this);
- }
- });
-
- session.registerGestureListener(mOnGestureListener);
- session.registerInputListener(ev -> onMotionEvent(ev));
-
- }
-
- private void onMotionEvent(InputEvent event) {
- if (!(event instanceof MotionEvent)) {
- Log.e(TAG, "non MotionEvent received:" + event);
- return;
- }
-
- final MotionEvent motionEvent = (MotionEvent) event;
-
- switch (motionEvent.getAction()) {
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- mTouchSession.pop();
- // If we are not capturing any input, there is no need to consider animating to
- // finish transition.
- if (mCapture == null || !mCapture) {
- break;
- }
-
- // We must capture the resulting velocities as resetMonitor() will clear these
- // values.
- mVelocityTracker.computeCurrentVelocity(1000);
- final float verticalVelocity = mVelocityTracker.getYVelocity();
- final float horizontalVelocity = mVelocityTracker.getXVelocity();
-
- final float velocityVector =
- (float) Math.hypot(horizontalVelocity, verticalVelocity);
-
- mExpanded = !flingRevealsOverlay(verticalVelocity, velocityVector);
- final float expansion = mExpanded
- ? KeyguardBouncerConstants.EXPANSION_VISIBLE
- : KeyguardBouncerConstants.EXPANSION_HIDDEN;
-
- // Log the swiping up to show Bouncer event.
- if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
- mUiEventLogger.log(DreamEvent.DREAM_SWIPED);
- }
-
- flingToExpansion(verticalVelocity, expansion);
- break;
- default:
- mVelocityTracker.addMovement(motionEvent);
- break;
- }
- }
-
- private ValueAnimator createExpansionAnimator(float targetExpansion) {
- final ValueAnimator animator =
- mValueAnimatorCreator.create(mCurrentExpansion, targetExpansion);
- animator.addUpdateListener(
- animation -> {
- float expansionFraction = (float) animation.getAnimatedValue();
- setPanelExpansion(expansionFraction);
- });
- if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
- animator.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mUiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
- }
- });
- }
- return animator;
- }
-
- protected boolean flingRevealsOverlay(float velocity, float velocityVector) {
- // Fully expand the space above the bouncer, if the user has expanded the bouncer less
- // than halfway or final velocity was positive, indicating a downward direction.
- if (Math.abs(velocityVector) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- return mCurrentExpansion > FLING_PERCENTAGE_THRESHOLD;
- } else {
- return velocity > 0;
- }
- }
-
- protected void flingToExpansion(float velocity, float expansion) {
- if (!mCentralSurfaces.isPresent()) {
- return;
- }
-
- // Don't set expansion if the user doesn't have a pin/password set.
- if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
- return;
- }
-
- // The animation utils deal in pixel units, rather than expansion height.
- final float viewHeight = mTouchSession.getBounds().height();
- final float currentHeight = viewHeight * mCurrentExpansion;
- final float targetHeight = viewHeight * expansion;
- final ValueAnimator animator = createExpansionAnimator(expansion);
- if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
- // Hides the bouncer, i.e., fully expands the space above the bouncer.
- mFlingAnimationUtilsClosing.apply(animator, currentHeight, targetHeight, velocity,
- viewHeight);
- } else {
- // Shows the bouncer, i.e., fully collapses the space above the bouncer.
- mFlingAnimationUtils.apply(
- animator, currentHeight, targetHeight, velocity, viewHeight);
- }
-
- animator.start();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
new file mode 100644
index 0000000..d5790a4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.kt
@@ -0,0 +1,371 @@
+/*
+ * 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.ambient.touch
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Rect
+import android.graphics.Region
+import android.util.Log
+import android.view.GestureDetector
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+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.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.wm.shell.animation.FlingAnimationUtils
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.math.abs
+import kotlin.math.hypot
+import kotlin.math.max
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/** Monitor for tracking touches on the DreamOverlay to bring up the bouncer. */
+class BouncerSwipeTouchHandler
+@Inject
+constructor(
+ scope: CoroutineScope,
+ private val scrimManager: ScrimManager,
+ private val centralSurfaces: Optional<CentralSurfaces>,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val valueAnimatorCreator: ValueAnimatorCreator,
+ private val velocityTrackerFactory: VelocityTrackerFactory,
+ private val lockPatternUtils: LockPatternUtils,
+ private val userTracker: UserTracker,
+ private val communalViewModel: CommunalViewModel,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+ private val flingAnimationUtils: FlingAnimationUtils,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+ private val flingAnimationUtilsClosing: FlingAnimationUtils,
+ @param:Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION)
+ private val bouncerZoneScreenPercentage: Float,
+ @param:Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE)
+ private val minBouncerZoneScreenPercentage: Float,
+ private val uiEventLogger: UiEventLogger,
+ private val activityStarter: ActivityStarter
+) : TouchHandler {
+ /** An interface for creating ValueAnimators. */
+ interface ValueAnimatorCreator {
+ /** Creates [ValueAnimator]. */
+ fun create(start: Float, finish: Float): ValueAnimator
+ }
+
+ /** An interface for obtaining VelocityTrackers. */
+ interface VelocityTrackerFactory {
+ /** Obtains [VelocityTracker]. */
+ fun obtain(): VelocityTracker?
+ }
+
+ private var currentScrimController: ScrimController? = null
+ private var currentExpansion = 0f
+ private var velocityTracker: VelocityTracker? = null
+ private var capture: Boolean? = null
+ private var expanded: Boolean = false
+ private var touchSession: TouchSession? = null
+ private val scrimManagerCallback =
+ ScrimManager.Callback { controller ->
+ currentScrimController?.reset()
+
+ currentScrimController = controller
+ }
+
+ /** Determines whether the touch handler should process touches in fullscreen swiping mode */
+ private var touchAvailable = false
+
+ private val onGestureListener: GestureDetector.OnGestureListener =
+ object : SimpleOnGestureListener() {
+ override fun onScroll(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ if (capture == null) {
+ capture =
+ if (Flags.dreamOverlayBouncerSwipeDirectionFiltering()) {
+ (abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
+ distanceY > 0) &&
+ if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+ } else {
+ // If the user scrolling favors a vertical direction, begin capturing
+ // scrolls.
+ abs(distanceY.toDouble()) > abs(distanceX.toDouble())
+ }
+ if (capture == true) {
+ // reset expanding
+ expanded = false
+ // Since the user is dragging the bouncer up, set scrimmed to false.
+ currentScrimController?.show()
+ }
+ }
+ if (capture != true) {
+ return false
+ }
+
+ if (!centralSurfaces.isPresent) {
+ return true
+ }
+
+ e1?.apply outer@{
+ // Don't set expansion for downward scroll.
+ if (y < e2.y) {
+ return true
+ }
+
+ // If scrolling up and keyguard is not locked, dismiss both keyguard and the
+ // dream since there's no bouncer to show.
+ if (y > e2.y && !lockPatternUtils.isSecure(userTracker.userId)) {
+ activityStarter.executeRunnableDismissingKeyguard(
+ { centralSurfaces.get().awakenDreams() },
+ /* cancelAction= */ null,
+ /* dismissShade= */ true,
+ /* afterKeyguardGone= */ true,
+ /* deferred= */ false
+ )
+ return true
+ }
+
+ // For consistency, we adopt the expansion definition found in the
+ // PanelViewController. In this case, expansion refers to the view above the
+ // 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).
+ touchSession?.apply {
+ val screenTravelPercentage =
+ (abs(([email protected] - e2.y).toDouble()) / getBounds().height()).toFloat()
+ setPanelExpansion(1 - screenTravelPercentage)
+ }
+ }
+
+ return true
+ }
+ }
+
+ init {
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ scope.launch {
+ communalViewModel.glanceableTouchAvailable.collect {
+ onGlanceableTouchAvailable(it)
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun onGlanceableTouchAvailable(available: Boolean) {
+ touchAvailable = available
+ }
+
+ private fun setPanelExpansion(expansion: Float) {
+ currentExpansion = expansion
+ val event =
+ ShadeExpansionChangeEvent(
+ /* fraction= */ currentExpansion,
+ /* expanded= */ expanded,
+ /* tracking= */ true
+ )
+ currentScrimController?.expand(event)
+ }
+
+ @VisibleForTesting
+ enum class DreamEvent(private val mId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The screensaver has been swiped up.") DREAM_SWIPED(988),
+ @UiEvent(doc = "The bouncer has become fully visible over dream.")
+ DREAM_BOUNCER_FULLY_VISIBLE(1056);
+
+ override fun getId(): Int {
+ return mId
+ }
+ }
+
+ override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
+ val width = bounds.width()
+ val height = bounds.height()
+ val minAllowableBottom = Math.round(height * (1 - minBouncerZoneScreenPercentage))
+ val normalRegion =
+ Rect(0, Math.round(height * (1 - bouncerZoneScreenPercentage)), width, height)
+
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ region.op(bounds, Region.Op.UNION)
+ exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
+ }
+
+ if (exclusionRect != null) {
+ val lowestBottom =
+ min(max(0.0, exclusionRect.bottom.toDouble()), minAllowableBottom.toDouble())
+ .toInt()
+ normalRegion.top = max(normalRegion.top.toDouble(), lowestBottom.toDouble()).toInt()
+ }
+ region.union(normalRegion)
+ }
+
+ override fun onSessionStart(session: TouchSession) {
+ velocityTracker = velocityTrackerFactory.obtain()
+ touchSession = session
+ velocityTracker?.apply { clear() }
+ if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+ notificationShadeWindowController.setForcePluginOpen(true, this)
+ }
+ scrimManager.addCallback(scrimManagerCallback)
+ currentScrimController = scrimManager.currentController
+ session.registerCallback {
+ velocityTracker?.apply { recycle() }
+ velocityTracker = null
+
+ scrimManager.removeCallback(scrimManagerCallback)
+ capture = null
+ touchSession = null
+ if (!Flags.communalBouncerDoNotModifyPluginOpen()) {
+ notificationShadeWindowController.setForcePluginOpen(false, this)
+ }
+ }
+ session.registerGestureListener(onGestureListener)
+ session.registerInputListener { ev: InputEvent -> onMotionEvent(ev) }
+ }
+
+ private fun onMotionEvent(event: InputEvent) {
+ if (event !is MotionEvent) {
+ Log.e(TAG, "non MotionEvent received:$event")
+ return
+ }
+ val motionEvent = event
+ when (motionEvent.action) {
+ MotionEvent.ACTION_CANCEL,
+ MotionEvent.ACTION_UP -> {
+ if (Flags.hubmodeFullscreenVerticalSwipe() && capture == true) {
+ communalViewModel.onResetTouchState()
+ }
+ touchSession?.apply { pop() }
+ // If we are not capturing any input, there is no need to consider animating to
+ // finish transition.
+ if (capture == null || !capture!!) {
+ return
+ }
+
+ // We must capture the resulting velocities as resetMonitor() will clear these
+ // values.
+ velocityTracker!!.computeCurrentVelocity(1000)
+ val verticalVelocity = velocityTracker!!.yVelocity
+ val horizontalVelocity = velocityTracker!!.xVelocity
+ val velocityVector =
+ hypot(horizontalVelocity.toDouble(), verticalVelocity.toDouble()).toFloat()
+ expanded = !flingRevealsOverlay(verticalVelocity, velocityVector)
+ val expansion =
+ if (expanded!!) KeyguardBouncerConstants.EXPANSION_VISIBLE
+ else KeyguardBouncerConstants.EXPANSION_HIDDEN
+
+ // Log the swiping up to show Bouncer event.
+ if (expansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
+ uiEventLogger.log(DreamEvent.DREAM_SWIPED)
+ }
+ flingToExpansion(verticalVelocity, expansion)
+ }
+ else -> velocityTracker!!.addMovement(motionEvent)
+ }
+ }
+
+ private fun createExpansionAnimator(targetExpansion: Float): ValueAnimator {
+ val animator = valueAnimatorCreator.create(currentExpansion, targetExpansion)
+ animator.addUpdateListener { animation: ValueAnimator ->
+ val expansionFraction = animation.animatedValue as Float
+ setPanelExpansion(expansionFraction)
+ }
+ if (targetExpansion == KeyguardBouncerConstants.EXPANSION_VISIBLE) {
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ uiEventLogger.log(DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE)
+ }
+ }
+ )
+ }
+ return animator
+ }
+
+ protected fun flingRevealsOverlay(velocity: Float, velocityVector: Float): Boolean {
+ // Fully expand the space above the bouncer, if the user has expanded the bouncer less
+ // than halfway or final velocity was positive, indicating a downward direction.
+ return if (abs(velocityVector.toDouble()) < flingAnimationUtils.minVelocityPxPerSecond) {
+ currentExpansion > FLING_PERCENTAGE_THRESHOLD
+ } else {
+ velocity > 0
+ }
+ }
+
+ protected fun flingToExpansion(velocity: Float, expansion: Float) {
+ if (!centralSurfaces.isPresent) {
+ return
+ }
+
+ // Don't set expansion if the user doesn't have a pin/password set.
+ if (!lockPatternUtils.isSecure(userTracker.userId)) {
+ return
+ }
+
+ touchSession?.apply {
+ // The animation utils deal in pixel units, rather than expansion height.
+ val viewHeight = getBounds().height().toFloat()
+ val currentHeight = viewHeight * currentExpansion
+ val targetHeight = viewHeight * expansion
+ val animator = createExpansionAnimator(expansion)
+ if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) {
+ // Hides the bouncer, i.e., fully expands the space above the bouncer.
+ flingAnimationUtilsClosing.apply(
+ animator,
+ currentHeight,
+ targetHeight,
+ velocity,
+ viewHeight
+ )
+ } else {
+ // Shows the bouncer, i.e., fully collapses the space above the bouncer.
+ flingAnimationUtils.apply(
+ animator,
+ currentHeight,
+ targetHeight,
+ velocity,
+ viewHeight
+ )
+ }
+ animator.start()
+ }
+ }
+
+ companion object {
+ const val FLING_PERCENTAGE_THRESHOLD = 0.5f
+ private const val TAG = "BouncerSwipeTouchHandler"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
deleted file mode 100644
index baca959..0000000
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
+++ /dev/null
@@ -1,132 +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.ambient.touch;
-
-import static com.android.systemui.ambient.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
-
-import android.app.DreamManager;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
-import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import java.util.Optional;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream
- * to bring down the shade.
- */
-public class ShadeTouchHandler implements TouchHandler {
- private final Optional<CentralSurfaces> mSurfaces;
- private final ShadeViewController mShadeViewController;
- private final DreamManager mDreamManager;
- private final int mInitiationHeight;
- private final CommunalSettingsInteractor
- mCommunalSettingsInteractor;
-
- /**
- * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
- */
- private Boolean mCapture;
-
- @Inject
- ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces,
- ShadeViewController shadeViewController,
- DreamManager dreamManager,
- CommunalSettingsInteractor communalSettingsInteractor,
- @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) {
- mSurfaces = centralSurfaces;
- mShadeViewController = shadeViewController;
- mDreamManager = dreamManager;
- mCommunalSettingsInteractor = communalSettingsInteractor;
- mInitiationHeight = initiationHeight;
- }
-
- @Override
- public void onSessionStart(TouchSession session) {
- if (mSurfaces.isEmpty()) {
- session.pop();
- return;
- }
-
- session.registerCallback(() -> mCapture = null);
-
- session.registerInputListener(ev -> {
- if (ev instanceof MotionEvent) {
- if (mCapture != null && mCapture) {
- sendTouchEvent((MotionEvent) ev);
- }
- if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP
- || ((MotionEvent) ev).getAction() == MotionEvent.ACTION_CANCEL) {
- session.pop();
- }
- }
- });
-
- session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() {
- @Override
- public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX,
- float distanceY) {
- if (mCapture == null) {
- // Only capture swipes that are going downwards.
- mCapture = Math.abs(distanceY) > Math.abs(distanceX) && distanceY < 0;
- if (mCapture) {
- // Send the initial touches over, as the input listener has already
- // processed these touches.
- sendTouchEvent(e1);
- sendTouchEvent(e2);
- }
- }
- return mCapture;
- }
-
- @Override
- public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX,
- float velocityY) {
- return mCapture;
- }
- });
- }
-
- private void sendTouchEvent(MotionEvent event) {
- if (mCommunalSettingsInteractor.isCommunalFlagEnabled() && !mDreamManager.isDreaming()) {
- // Send touches to central surfaces only when on the glanceable hub while not dreaming.
- // While sending touches where while dreaming will open the shade, the shade
- // while closing if opened then closed in the same gesture.
- mSurfaces.get().handleExternalShadeWindowTouch(event);
- } else {
- // Send touches to the shade view when dreaming.
- mShadeViewController.handleExternalTouch(event);
- }
- }
-
- @Override
- public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
- final Rect outBounds = new Rect(bounds);
- outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight);
- region.op(outBounds, Region.Op.UNION);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
new file mode 100644
index 0000000..06b41de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.kt
@@ -0,0 +1,157 @@
+/*
+ * 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.ambient.touch
+
+import android.app.DreamManager
+import android.graphics.Rect
+import android.graphics.Region
+import android.view.GestureDetector.SimpleOnGestureListener
+import android.view.InputEvent
+import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Flags
+import com.android.systemui.ambient.touch.TouchHandler.TouchSession
+import com.android.systemui.ambient.touch.dagger.ShadeModule
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.math.abs
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * [ShadeTouchHandler] is responsible for handling swipe down gestures over dream to bring down the
+ * shade.
+ */
+class ShadeTouchHandler
+@Inject
+constructor(
+ scope: CoroutineScope,
+ private val surfaces: Optional<CentralSurfaces>,
+ private val shadeViewController: ShadeViewController,
+ private val dreamManager: DreamManager,
+ private val communalViewModel: CommunalViewModel,
+ private val communalSettingsInteractor: CommunalSettingsInteractor,
+ @param:Named(ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT)
+ private val initiationHeight: Int
+) : TouchHandler {
+ /**
+ * Tracks whether or not we are capturing a given touch. Will be null before and after a touch.
+ */
+ private var capture: Boolean? = null
+
+ /** Determines whether the touch handler should process touches in fullscreen swiping mode */
+ private var touchAvailable = false
+
+ init {
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ scope.launch {
+ communalViewModel.glanceableTouchAvailable.collect {
+ onGlanceableTouchAvailable(it)
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun onGlanceableTouchAvailable(available: Boolean) {
+ touchAvailable = available
+ }
+
+ override fun onSessionStart(session: TouchSession) {
+ if (surfaces.isEmpty) {
+ session.pop()
+ return
+ }
+ session.registerCallback { capture = null }
+ session.registerInputListener { ev: InputEvent? ->
+ if (ev is MotionEvent) {
+ if (capture == true) {
+ sendTouchEvent(ev)
+ }
+ if (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) {
+ if (capture == true) {
+ communalViewModel.onResetTouchState()
+ }
+ session.pop()
+ }
+ }
+ }
+ session.registerGestureListener(
+ object : SimpleOnGestureListener() {
+ override fun onScroll(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ if (capture == null) {
+ // Only capture swipes that are going downwards.
+ capture =
+ abs(distanceY.toDouble()) > abs(distanceX.toDouble()) &&
+ distanceY < 0 &&
+ if (Flags.hubmodeFullscreenVerticalSwipe()) touchAvailable else true
+ if (capture == true) {
+ // Send the initial touches over, as the input listener has already
+ // processed these touches.
+ e1?.apply { sendTouchEvent(this) }
+ sendTouchEvent(e2)
+ }
+ }
+ return capture == true
+ }
+
+ override fun onFling(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ velocityX: Float,
+ velocityY: Float
+ ): Boolean {
+ return capture == true
+ }
+ }
+ )
+ }
+
+ private fun sendTouchEvent(event: MotionEvent) {
+ if (communalSettingsInteractor.isCommunalFlagEnabled() && !dreamManager.isDreaming) {
+ // Send touches to central surfaces only when on the glanceable hub while not dreaming.
+ // While sending touches where while dreaming will open the shade, the shade
+ // while closing if opened then closed in the same gesture.
+ surfaces.get().handleExternalShadeWindowTouch(event)
+ } else {
+ // Send touches to the shade view when dreaming.
+ shadeViewController.handleExternalTouch(event)
+ }
+ }
+
+ override fun getTouchInitiationRegion(bounds: Rect, region: Region, exclusionRect: Rect?) {
+ // If fullscreen swipe, use entire space minus exclusion region
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ region.op(bounds, Region.Op.UNION)
+
+ exclusionRect?.apply { region.op(this, Region.Op.DIFFERENCE) }
+ }
+
+ val outBounds = Rect(bounds)
+ outBounds.inset(0, 0, 0, outBounds.height() - initiationHeight)
+ region.op(outBounds, Region.Op.UNION)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
index a4924d1..ae21e56 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
@@ -17,12 +17,14 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.coroutineScope
import com.android.systemui.ambient.dagger.AmbientModule
import com.android.systemui.ambient.touch.TouchHandler
import dagger.Module
import dagger.Provides
import dagger.multibindings.ElementsIntoSet
import javax.inject.Named
+import kotlinx.coroutines.CoroutineScope
@Module
interface AmbientTouchModule {
@@ -33,6 +35,12 @@
return lifecycleOwner.lifecycle
}
+ @JvmStatic
+ @Provides
+ fun providesLifecycleScope(lifecycle: Lifecycle): CoroutineScope {
+ return lifecycle.coroutineScope
+ }
+
@Provides
@ElementsIntoSet
fun providesDreamTouchHandlers(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3dd3758..5ffb9ab2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -56,13 +56,13 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
@@ -147,7 +147,7 @@
private final Execution mExecution;
private final FingerprintManager mFingerprintManager;
@NonNull private final LayoutInflater mInflater;
- private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mWindowManager;
private final DelayableExecutor mFgExecutor;
@NonNull private final Executor mBiometricExecutor;
@NonNull private final StatusBarStateController mStatusBarStateController;
@@ -693,7 +693,7 @@
@NonNull Execution execution,
@NonNull LayoutInflater inflater,
@Nullable FingerprintManager fingerprintManager,
- @NonNull WindowManager windowManager,
+ @NonNull ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
@NonNull StatusBarStateController statusBarStateController,
@Main DelayableExecutor fgExecutor,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -741,7 +741,7 @@
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
// fingerprint manager should never be null.
mFingerprintManager = checkNotNull(fingerprintManager);
- mWindowManager = windowManager;
+ mWindowManager = viewCaptureAwareWindowManager;
mFgExecutor = fgExecutor;
mStatusBarStateController = statusBarStateController;
mKeyguardStateController = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index e03d160..1bac0bc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -44,6 +44,7 @@
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
import androidx.annotation.LayoutRes
import androidx.annotation.VisibleForTesting
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
@@ -94,7 +95,7 @@
constructor(
private val context: Context,
private val inflater: LayoutInflater,
- private val windowManager: WindowManager,
+ private val windowManager: ViewCaptureAwareWindowManager,
private val accessibilityManager: AccessibilityManager,
private val statusBarStateController: StatusBarStateController,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 430887d..ecfbd66 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -24,7 +24,6 @@
import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.Flags
import android.hardware.face.FaceManager
-import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
@@ -124,7 +123,6 @@
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
subtitleView.isSelected =
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
- descriptionView.movementMethod = ScrollingMovementMethod()
val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 3904ee1..b1cba2f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -915,7 +915,12 @@
event: MotionEvent,
touchExplorationEnabled: Boolean,
): Boolean {
- if (bpTalkback() && modalities.first().hasUdfps && touchExplorationEnabled) {
+ if (
+ bpTalkback() &&
+ modalities.first().hasUdfps &&
+ touchExplorationEnabled &&
+ !isAuthenticated.first().isAuthenticated
+ ) {
// TODO(b/315184924): Remove uses of UdfpsUtils
val scaledTouch =
udfpsUtils.getTouchInNativeCoordinates(
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 40a141d..e2089bb 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -24,7 +24,6 @@
import androidx.compose.ui.input.key.type
import androidx.core.graphics.drawable.toBitmap
import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
@@ -43,7 +42,6 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
-import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -67,7 +65,9 @@
/** Holds UI state and handles user input on bouncer UIs. */
class BouncerViewModel(
@Application private val applicationContext: Context,
- @Application private val applicationScope: CoroutineScope,
+ @Deprecated("TODO(b/354270224): remove this. Injecting CoroutineScope to view-models is banned")
+ @Application
+ private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
private val bouncerInteractor: BouncerInteractor,
private val inputMethodInteractor: InputMethodInteractor,
@@ -91,14 +91,13 @@
initialValue = null,
)
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- bouncerInteractor.dismissDestination
- .map(::destinationSceneMap)
- .stateIn(
- applicationScope,
- SharingStarted.WhileSubscribed(),
- initialValue = destinationSceneMap(Scenes.Lockscreen),
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ bouncerInteractor.dismissDestination.map { prevScene ->
+ mapOf(
+ Back to UserActionResult(prevScene),
+ Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
)
+ }
val message: BouncerMessageViewModel = bouncerMessageViewModel
@@ -328,8 +327,7 @@
{ message },
failedAttempts,
remainingAttempts,
- )
- ?: message
+ ) ?: message
} else {
message
}
@@ -346,8 +344,7 @@
.KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
{ message },
failedAttempts,
- )
- ?: message
+ ) ?: message
} else {
message
}
@@ -375,12 +372,6 @@
}
}
- private fun destinationSceneMap(prevScene: SceneKey) =
- mapOf(
- Back to UserActionResult(prevScene),
- Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
- )
-
/**
* Notifies that a key event has occurred.
*
@@ -390,8 +381,7 @@
return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
keyEvent.type,
keyEvent.nativeKeyEvent.keyCode
- )
- ?: false
+ ) ?: false
}
data class DialogViewModel(
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index ecbd3f9..6757edb 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -19,6 +19,7 @@
import android.app.ActivityManager
import android.app.ActivityOptions
import android.app.IActivityTaskManager
+import android.app.admin.DevicePolicyManager
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
@@ -32,8 +33,8 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -45,9 +46,10 @@
* the camera).
*/
@SysUISingleton
-class CameraGestureHelper @Inject constructor(
+class CameraGestureHelper
+@Inject
+constructor(
private val context: Context,
- private val centralSurfaces: CentralSurfaces,
private val keyguardStateController: KeyguardStateController,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
private val packageManager: PackageManager,
@@ -59,24 +61,25 @@
private val contentResolver: ContentResolver,
@Main private val uiExecutor: Executor,
private val selectedUserInteractor: SelectedUserInteractor,
+ private val devicePolicyManager: DevicePolicyManager,
+ private val lockscreenUserManager: NotificationLockscreenUserManager,
) {
- /**
- * Whether the camera application can be launched for the camera launch gesture.
- */
+ /** Whether the camera application can be launched for the camera launch gesture. */
fun canCameraGestureBeLaunched(statusBarState: Int): Boolean {
- if (!centralSurfaces.isCameraAllowedByAdmin) {
+ if (!isCameraAllowedByAdmin()) {
return false
}
- val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
- getStartCameraIntent(selectedUserInteractor.getSelectedUserId()),
- PackageManager.MATCH_DEFAULT_ONLY,
- selectedUserInteractor.getSelectedUserId()
- )
+ val resolveInfo: ResolveInfo? =
+ packageManager.resolveActivityAsUser(
+ getStartCameraIntent(selectedUserInteractor.getSelectedUserId()),
+ PackageManager.MATCH_DEFAULT_ONLY,
+ selectedUserInteractor.getSelectedUserId()
+ )
val resolvedPackage = resolveInfo?.activityInfo?.packageName
return (resolvedPackage != null &&
- (statusBarState != StatusBarState.SHADE ||
- !activityManager.isInForeground(resolvedPackage)))
+ (statusBarState != StatusBarState.SHADE ||
+ !activityManager.isInForeground(resolvedPackage)))
}
/**
@@ -87,9 +90,11 @@
fun launchCamera(source: Int) {
val intent: Intent = getStartCameraIntent(selectedUserInteractor.getSelectedUserId())
intent.putExtra(CameraIntents.EXTRA_LAUNCH_SOURCE, source)
- val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
- intent, selectedUserInteractor.getSelectedUserId()
- )
+ val wouldLaunchResolverActivity =
+ activityIntentHelper.wouldLaunchResolverActivity(
+ intent,
+ selectedUserInteractor.getSelectedUserId()
+ )
if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
uiExecutor.execute {
// Normally an activity will set its requested rotation animation on its window.
@@ -101,7 +106,7 @@
val activityOptions = ActivityOptions.makeBasic()
activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true)
activityOptions.rotationAnimationHint =
- WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+ WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
try {
activityTaskManager.startActivityAsUser(
null,
@@ -118,11 +123,7 @@
selectedUserInteractor.getSelectedUserId(true),
)
} catch (e: RemoteException) {
- Log.w(
- "CameraGestureHelper",
- "Unable to start camera activity",
- e
- )
+ Log.w("CameraGestureHelper", "Unable to start camera activity", e)
}
}
} else {
@@ -131,9 +132,6 @@
activityStarter.startActivity(intent, false /* dismissShade */)
}
- // Call this to make sure that the keyguard returns if the app that is being launched
- // crashes after a timeout.
- centralSurfaces.startLaunchTransitionTimeout()
// Call this to make sure the keyguard is ready to be dismissed once the next intent is
// handled by the OS (in our case it is the activity we started right above)
statusBarKeyguardViewManager.readyForKeyguardDone()
@@ -152,4 +150,17 @@
cameraIntents.getInsecureCameraIntent(userId)
}
}
+
+ private fun isCameraAllowedByAdmin(): Boolean {
+ if (devicePolicyManager.getCameraDisabled(null, lockscreenUserManager.getCurrentUserId())) {
+ return false
+ } else if (keyguardStateController.isShowing() && statusBarKeyguardViewManager.isSecure()) {
+ // Check if the admin has disabled the camera specifically for the keyguard
+ return (devicePolicyManager.getKeyguardDisabledFeatures(
+ null,
+ lockscreenUserManager.getCurrentUserId()
+ ) and DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0
+ }
+ return true
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index e0e1971..adb1ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -25,6 +25,7 @@
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
@@ -74,6 +75,13 @@
return onAnyConfigurationChange.mapLatest { repository.getDimensionPixelSize(resourceId) }
}
+ /** Emits the dimensional pixel size of the given resource, inverting it for RTL if necessary */
+ fun directionalDimensionPixelSize(originLayoutDirection: Int, resourceId: Int): Flow<Int> {
+ return dimensionPixelSize(resourceId).combine(layoutDirection) { size, direction ->
+ if (originLayoutDirection == direction) size else -size
+ }
+ }
+
/** Given a set of [resourceId]s, emit Map<ResourceId, DimensionPixelSize> on config change */
fun dimensionPixelSize(resourceIds: Set<Int>): Flow<Map<Int, Int>> {
return onAnyConfigurationChange.mapLatest {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index b8601ec..4be93cc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -29,9 +29,12 @@
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flowOf
/** The base view model for the communal hub. */
@@ -57,6 +60,26 @@
val selectedKey: StateFlow<String?>
get() = _selectedKey
+ private val _isTouchConsumed: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Whether an element inside the lazy grid is actively consuming touches */
+ val isTouchConsumed: Flow<Boolean> = _isTouchConsumed.asStateFlow()
+
+ private val _isNestedScrolling: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Whether the lazy grid is reporting scrolling within itself */
+ val isNestedScrolling: Flow<Boolean> = _isNestedScrolling.asStateFlow()
+
+ /**
+ * Whether touch is available to be consumed by a touch handler. Touch is available during
+ * nested scrolling as lazy grid reports this for all scroll directions that it detects. In the
+ * case that there is consumed scrolling on a nested element, such as an AndroidView, no nested
+ * scrolling will be reported. It is up to the flow consumer to determine whether the nested
+ * scroll can be applied. In the communal case, this would be identifying the scroll as
+ * vertical, which the lazy horizontal grid does not handle.
+ */
+ val glanceableTouchAvailable: Flow<Boolean> = anyOf(not(isTouchConsumed), isNestedScrolling)
+
/** Accessibility delegate to be set on CommunalAppWidgetHostView. */
open val widgetAccessibilityDelegate: View.AccessibilityDelegate? = null
@@ -200,4 +223,28 @@
fun setSelectedKey(key: String?) {
_selectedKey.value = key
}
+
+ /** Invoked once touches inside the lazy grid are consumed */
+ fun onHubTouchConsumed() {
+ if (_isTouchConsumed.value) {
+ return
+ }
+
+ _isTouchConsumed.value = true
+ }
+
+ /** Invoked when nested scrolling begins on the lazy grid */
+ fun onNestedScrolling() {
+ if (_isNestedScrolling.value) {
+ return
+ }
+
+ _isNestedScrolling.value = true
+ }
+
+ /** Resets nested scroll and touch consumption state */
+ fun onResetTouchState() {
+ _isTouchConsumed.value = false
+ _isNestedScrolling.value = false
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/DensityUtils.kt b/packages/SystemUI/src/com/android/systemui/communal/util/DensityUtils.kt
new file mode 100644
index 0000000..57be7b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/DensityUtils.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.util
+
+import android.view.Display
+import android.view.WindowManagerGlobal
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/**
+ * [DensityUtils] helps convert dp defined values to be consistent regardless of the set density.
+ */
+class DensityUtils {
+ companion object {
+ val Int.adjustedDp: Dp
+ get() = this.dp * scalingAdjustment
+
+ private val windowManagerService = WindowManagerGlobal.getWindowManagerService()
+ val scalingAdjustment
+ get() =
+ windowManagerService?.let { wm ->
+ wm.getInitialDisplayDensity(Display.DEFAULT_DISPLAY).toFloat() /
+ wm.getBaseDisplayDensity(Display.DEFAULT_DISPLAY)
+ } ?: 1F
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 3985769..03ef17b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -128,7 +128,7 @@
Box(
modifier =
Modifier.fillMaxSize()
- .background(LocalAndroidColorScheme.current.onSecondaryFixed),
+ .background(LocalAndroidColorScheme.current.surfaceDim),
) {
CommunalHub(
viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
index abda44b..87aa5e2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
@@ -24,6 +24,7 @@
import android.view.View
import android.view.ViewGroup
import androidx.core.os.BuildCompat.isAtLeastS
+import com.android.systemui.communal.util.DensityUtils
import com.android.systemui.res.R
import kotlin.math.min
@@ -82,7 +83,8 @@
/** Get the radius of the rounded rectangle defined in the host's resource. */
private fun getOwnedEnforcedRadius(context: Context): Float {
val res: Resources = context.resources
- return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius)
+ return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius) *
+ DensityUtils.scalingAdjustment
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index 04fda33..ee7b6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -20,6 +20,7 @@
import android.graphics.Rect;
import android.graphics.Region;
+import android.util.LayoutDirection;
import android.view.GestureDetector;
import android.view.MotionEvent;
@@ -27,6 +28,7 @@
import androidx.lifecycle.Lifecycle;
import com.android.systemui.ambient.touch.TouchHandler;
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -43,30 +45,42 @@
private final Optional<CentralSurfaces> mCentralSurfaces;
private final Lifecycle mLifecycle;
private final CommunalInteractor mCommunalInteractor;
+
+ private final ConfigurationInteractor mConfigurationInteractor;
private Boolean mIsEnabled = false;
+ private int mLayoutDirection = LayoutDirection.LTR;
+
@VisibleForTesting
- final Consumer<Boolean> mIsCommunalAvailableCallback =
- isAvailable -> {
- setIsEnabled(isAvailable);
- };
+ final Consumer<Boolean> mIsCommunalAvailableCallback = isAvailable -> setIsEnabled(isAvailable);
+
+ @VisibleForTesting
+ final Consumer<Integer> mLayoutDirectionCallback = direction -> mLayoutDirection = direction;
@Inject
public CommunalTouchHandler(
Optional<CentralSurfaces> centralSurfaces,
@Named(CommunalTouchModule.COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth,
CommunalInteractor communalInteractor,
+ ConfigurationInteractor configurationInteractor,
Lifecycle lifecycle) {
mInitiationWidth = initiationWidth;
mCentralSurfaces = centralSurfaces;
mLifecycle = lifecycle;
mCommunalInteractor = communalInteractor;
+ mConfigurationInteractor = configurationInteractor;
collectFlow(
mLifecycle,
mCommunalInteractor.isCommunalAvailable(),
mIsCommunalAvailableCallback
);
+
+ collectFlow(
+ mLifecycle,
+ mConfigurationInteractor.getLayoutDirection(),
+ mLayoutDirectionCallback
+ );
}
@Override
@@ -90,7 +104,15 @@
@Override
public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
final Rect outBounds = new Rect(bounds);
- outBounds.inset(outBounds.width() - mInitiationWidth, 0, 0, 0);
+ final int inset = outBounds.width() - mInitiationWidth;
+
+ // Touch initiation area is defined in terms of LTR. The insets must be flipped for RTL
+ if (mLayoutDirection == LayoutDirection.LTR) {
+ outBounds.inset(inset, 0, 0, 0);
+ } else {
+ outBounds.inset(0, 0, inset, 0);
+ }
+
region.op(outBounds, Region.Op.UNION);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 0e06117..32530d6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -51,6 +51,7 @@
// Internal notification backend dependencies
crossAppPoliteNotifications dependsOn politeNotifications
vibrateWhileUnlockedToken dependsOn politeNotifications
+ modesUi dependsOn modesApi
// Internal notification frontend dependencies
NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token
@@ -85,6 +86,12 @@
private inline val vibrateWhileUnlockedToken: FlagToken
get() = FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
+ private inline val modesUi
+ get() = FlagToken(android.app.Flags.FLAG_MODES_UI, android.app.Flags.modesUi())
+
+ private inline val modesApi
+ get() = FlagToken(android.app.Flags.FLAG_MODES_API, android.app.Flags.modesApi())
+
private inline val communalHub
get() = FlagToken(FLAG_COMMUNAL_HUB, communalHub())
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d0beb7a..8990505 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -292,15 +292,6 @@
val WM_ENABLE_SHELL_TRANSITIONS =
sysPropBooleanFlag("persist.wm.debug.shell_transit", default = true)
- // TODO(b/254513207): Tracking Bug
- @Keep
- @JvmField
- val WM_ENABLE_PARTIAL_SCREEN_SHARING =
- releasedFlag(
- name = "enable_record_task_content",
- namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
- )
-
// TODO(b/256873975): Tracking Bug
@JvmField
@Keep
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 491c73d..4652b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -26,7 +26,9 @@
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.QSLog
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -48,7 +50,7 @@
constructor(
private val vibratorHelper: VibratorHelper?,
private val keyguardStateController: KeyguardStateController,
- private val falsingManager: FalsingManager,
+ @QSLog private val logBuffer: LogBuffer,
) {
var effectDuration = 0
@@ -103,6 +105,7 @@
}
fun handleActionDown() {
+ logEvent(qsTile?.tileSpec, state, "action down received")
when (state) {
State.IDLE -> {
setState(State.TIMEOUT_WAIT)
@@ -114,6 +117,7 @@
}
fun handleActionUp() {
+ logEvent(qsTile?.tileSpec, state, "action up received")
if (state == State.RUNNING_FORWARD) {
setState(State.RUNNING_BACKWARDS_FROM_UP)
callback?.onReverseAnimator()
@@ -132,6 +136,7 @@
}
fun handleAnimationStart() {
+ logEvent(qsTile?.tileSpec, state, "animation started")
if (state == State.TIMEOUT_WAIT) {
vibrate(longPressHint)
setState(State.RUNNING_FORWARD)
@@ -140,6 +145,7 @@
/** This function is called both when an animator completes or gets cancelled */
fun handleAnimationComplete() {
+ logEvent(qsTile?.tileSpec, state, "animation completed")
when (state) {
State.RUNNING_FORWARD -> {
vibrate(snapEffect)
@@ -149,11 +155,13 @@
callback?.onResetProperties()
setState(State.IDLE)
}
+ logEvent(qsTile?.tileSpec, state, "long click action triggered")
qsTile?.longClick(expandable)
}
State.RUNNING_BACKWARDS_FROM_UP -> {
callback?.onEffectFinishedReversing()
setState(getStateForClick())
+ logEvent(qsTile?.tileSpec, state, "click action triggered")
qsTile?.click(expandable)
}
State.RUNNING_BACKWARDS_FROM_CANCEL -> {
@@ -181,6 +189,7 @@
if (keyguardStateController.isPrimaryBouncerShowing || !isStateClickable) return false
setState(getStateForClick())
+ logEvent(qsTile?.tileSpec, state, "click action triggered")
qsTile?.click(expandable)
return true
}
@@ -195,11 +204,8 @@
@VisibleForTesting
fun getStateForClick(): State {
val isTileUnavailable = qsTile?.state?.state == Tile.STATE_UNAVAILABLE
- val isFalseTapWhileLocked =
- !keyguardStateController.isUnlocked &&
- falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
val handlesLongClick = qsTile?.state?.handlesLongClick == true
- return if (isTileUnavailable || isFalseTapWhileLocked || !handlesLongClick) {
+ return if (isTileUnavailable || !handlesLongClick || keyguardStateController.isShowing) {
// The click event will not perform an action that resets the state. Therefore, this is
// the last opportunity to reset the state back to IDLE.
State.IDLE
@@ -278,6 +284,20 @@
return delegated
}
+ private fun logEvent(tileSpec: String?, state: State, event: String) {
+ if (!DEBUG) return
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = tileSpec
+ str2 = event
+ str3 = state.name
+ },
+ { "[long-press effect on $str1 tile] $str2 on state: $str3" }
+ )
+ }
+
enum class State {
IDLE, /* The effect is idle waiting for touch input */
TIMEOUT_WAIT, /* The effect is waiting for a tap timeout period */
@@ -309,4 +329,9 @@
/** Cancel the effect animator */
fun onCancelAnimator()
}
+
+ companion object {
+ private const val TAG = "QSLongPressEffect"
+ private const val DEBUG = true
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
index bdc18b3..0e19d87 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/model/InputMethodModel.kt
@@ -22,6 +22,8 @@
* @see android.view.inputmethod.InputMethodInfo
*/
data class InputMethodModel(
+ /** A unique ID for the user associated with this input method. */
+ val userId: Int,
/** A unique ID for this input method. */
val imeId: String,
/** The subtypes of this IME (may be empty). */
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
index 5f316c4..c6fdc32 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/data/repository/InputMethodRepository.kt
@@ -18,7 +18,6 @@
import android.annotation.SuppressLint
import android.os.UserHandle
-import android.view.inputmethod.InputMethodInfo
import android.view.inputmethod.InputMethodManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -34,18 +33,27 @@
/** Provides access to input-method related application state in the bouncer. */
interface InputMethodRepository {
+
/**
* Creates and returns a new `Flow` of installed input methods that are enabled for the
* specified user.
*
+ * @param user The user to query.
* @param fetchSubtypes Whether to fetch the IME Subtypes as well (requires an additional IPC
* call for each IME, avoid if not needed).
* @see InputMethodManager.getEnabledInputMethodListAsUser
*/
- suspend fun enabledInputMethods(userId: Int, fetchSubtypes: Boolean): Flow<InputMethodModel>
+ suspend fun enabledInputMethods(
+ user: UserHandle,
+ fetchSubtypes: Boolean,
+ ): Flow<InputMethodModel>
- /** Returns enabled subtypes for the currently selected input method. */
- suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype>
+ /**
+ * Returns enabled subtypes for the currently selected input method.
+ *
+ * @param user The user to query.
+ */
+ suspend fun selectedInputMethodSubtypes(user: UserHandle): List<InputMethodModel.Subtype>
/**
* Shows the system's input method picker dialog.
@@ -67,20 +75,22 @@
) : InputMethodRepository {
override suspend fun enabledInputMethods(
- userId: Int,
+ user: UserHandle,
fetchSubtypes: Boolean
): Flow<InputMethodModel> {
return withContext(backgroundDispatcher) {
- inputMethodManager.getEnabledInputMethodListAsUser(UserHandle.of(userId))
+ inputMethodManager.getEnabledInputMethodListAsUser(user)
}
.asFlow()
.map { inputMethodInfo ->
InputMethodModel(
+ userId = user.identifier,
imeId = inputMethodInfo.id,
subtypes =
if (fetchSubtypes) {
enabledInputMethodSubtypes(
- inputMethodInfo,
+ user = user,
+ imeId = inputMethodInfo.id,
allowsImplicitlyEnabledSubtypes = true
)
} else {
@@ -90,11 +100,19 @@
}
}
- override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> {
- return enabledInputMethodSubtypes(
- inputMethodInfo = null, // Fetch subtypes for the currently-selected IME.
- allowsImplicitlyEnabledSubtypes = false
- )
+ override suspend fun selectedInputMethodSubtypes(
+ user: UserHandle,
+ ): List<InputMethodModel.Subtype> {
+ val selectedIme = inputMethodManager.getCurrentInputMethodInfoAsUser(user)
+ return if (selectedIme == null) {
+ emptyList()
+ } else {
+ enabledInputMethodSubtypes(
+ user = user,
+ imeId = selectedIme.id,
+ allowsImplicitlyEnabledSubtypes = false
+ )
+ }
}
@SuppressLint("MissingPermission")
@@ -107,21 +125,23 @@
/**
* Returns a list of enabled input method subtypes for the specified input method info.
*
- * @param inputMethodInfo The [InputMethodInfo] whose subtypes list will be returned. If `null`,
- * returns enabled subtypes for the currently selected [InputMethodInfo].
+ * @param user The user to query.
+ * @param imeId The ID of the input method whose subtypes list will be returned.
* @param allowsImplicitlyEnabledSubtypes Whether to allow to return the implicitly enabled
* subtypes. If an input method info doesn't have enabled subtypes, the framework will
* implicitly enable subtypes according to the current system language.
- * @see InputMethodManager.getEnabledInputMethodSubtypeList
+ * @see InputMethodManager.getEnabledInputMethodSubtypeListAsUser
*/
private suspend fun enabledInputMethodSubtypes(
- inputMethodInfo: InputMethodInfo?,
+ user: UserHandle,
+ imeId: String,
allowsImplicitlyEnabledSubtypes: Boolean
): List<InputMethodModel.Subtype> {
return withContext(backgroundDispatcher) {
- inputMethodManager.getEnabledInputMethodSubtypeList(
- inputMethodInfo,
- allowsImplicitlyEnabledSubtypes
+ inputMethodManager.getEnabledInputMethodSubtypeListAsUser(
+ imeId,
+ allowsImplicitlyEnabledSubtypes,
+ user
)
}
.map {
diff --git a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
index c54aa7f..d3ef178 100644
--- a/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputmethod/domain/interactor/InputMethodInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.inputmethod.domain.interactor
+import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputmethod.data.repository.InputMethodRepository
import javax.inject.Inject
@@ -36,14 +37,16 @@
* Method adapted from `com.android.inputmethod.latin.Utils`.
*/
suspend fun hasMultipleEnabledImesOrSubtypes(userId: Int): Boolean {
+ val user = UserHandle.of(userId)
// Count IMEs that either have no subtypes, or have at least one non-auxiliary subtype.
val matchingInputMethods =
repository
- .enabledInputMethods(userId, fetchSubtypes = true)
+ .enabledInputMethods(user, fetchSubtypes = true)
.filter { ime -> ime.subtypes.isEmpty() || ime.subtypes.any { !it.isAuxiliary } }
.take(2) // Short-circuit if we find at least 2 matching IMEs.
- return matchingInputMethods.count() > 1 || repository.selectedInputMethodSubtypes().size > 1
+ return matchingInputMethods.count() > 1 ||
+ repository.selectedInputMethodSubtypes(user).size > 1
}
/** Shows the system's input method picker dialog. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
index f649be2..b859cdc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
@@ -20,6 +20,7 @@
import android.graphics.Paint
import android.graphics.PixelFormat
import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyboard.docking.ui.KeyboardDockingIndicationView
@@ -37,7 +38,7 @@
context: Context,
@Application private val applicationScope: CoroutineScope,
private val viewModel: KeyboardDockingIndicationViewModel,
- private val windowManager: WindowManager
+ private val windowManager: ViewCaptureAwareWindowManager,
) {
private val windowLayoutParams =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index be64ff60..af755d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -384,16 +384,7 @@
if (index > 0) {
HorizontalDivider()
}
- ShortcutSinglePane(searchQuery, shortcut)
- }
-}
-
-@Composable
-private fun ShortcutSinglePane(searchQuery: String, shortcut: Shortcut) {
- Column(Modifier.padding(vertical = 24.dp)) {
- ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
- Spacer(modifier = Modifier.height(12.dp))
- ShortcutKeyCombinations(shortcut = shortcut)
+ ShortcutView(Modifier.padding(vertical = 24.dp), searchQuery, shortcut)
}
}
@@ -421,7 +412,7 @@
onCategoryClicked = { onCategorySelected(it.type) }
)
Spacer(modifier = Modifier.width(24.dp))
- EndSidePanel(searchQuery, Modifier.fillMaxSize(), selectedCategory)
+ EndSidePanel(searchQuery, Modifier.fillMaxSize().padding(top = 8.dp), selectedCategory)
}
}
}
@@ -447,14 +438,14 @@
shape = RoundedCornerShape(28.dp),
color = MaterialTheme.colorScheme.surfaceBright
) {
- Column(Modifier.padding(horizontal = 32.dp, vertical = 24.dp)) {
+ Column(Modifier.padding(24.dp)) {
SubCategoryTitle(subCategory.label)
- Spacer(Modifier.height(24.dp))
+ Spacer(Modifier.height(8.dp))
subCategory.shortcuts.fastForEachIndexed { index, shortcut ->
if (index > 0) {
HorizontalDivider()
}
- ShortcutViewDualPane(searchQuery, shortcut)
+ ShortcutView(Modifier.padding(vertical = 16.dp), searchQuery, shortcut)
}
}
}
@@ -470,17 +461,17 @@
}
@Composable
-private fun ShortcutViewDualPane(searchQuery: String, shortcut: Shortcut) {
- Row(Modifier.padding(vertical = 16.dp)) {
+private fun ShortcutView(modifier: Modifier, searchQuery: String, shortcut: Shortcut) {
+ Row(modifier) {
Row(
- modifier = Modifier.width(160.dp).align(Alignment.CenterVertically),
+ modifier = Modifier.width(128.dp).align(Alignment.CenterVertically),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (shortcut.icon != null) {
ShortcutIcon(
shortcut.icon,
- modifier = Modifier.size(36.dp),
+ modifier = Modifier.size(24.dp),
)
}
ShortcutDescriptionText(
@@ -520,7 +511,11 @@
modifier: Modifier = Modifier,
shortcut: Shortcut,
) {
- FlowRow(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ FlowRow(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ horizontalArrangement = Arrangement.End
+ ) {
shortcut.commands.forEachIndexed { index, command ->
if (index > 0) {
ShortcutOrSeparator(spacing = 16.dp)
@@ -641,7 +636,7 @@
) {
Column(modifier) {
ShortcutsSearchBar(onSearchQueryChanged)
- Spacer(modifier = Modifier.heightIn(16.dp))
+ Spacer(modifier = Modifier.heightIn(8.dp))
CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked)
Spacer(modifier = Modifier.weight(1f))
KeyboardSettings(onKeyboardSettingsClicked)
@@ -678,7 +673,7 @@
Surface(
selected = selected,
onClick = onClick,
- modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 72.dp).fillMaxWidth(),
+ modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 64.dp).fillMaxWidth(),
shape = RoundedCornerShape(28.dp),
color = colors.containerColor(selected).value,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
index b8d0c23..2039743 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
@@ -26,8 +26,10 @@
import androidx.activity.BackEventCompat
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalContext
import androidx.core.view.updatePadding
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.flowWithLifecycle
@@ -36,6 +38,7 @@
import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelper
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
@@ -49,6 +52,7 @@
class ShortcutHelperActivity
@Inject
constructor(
+ private val userTracker: UserTracker,
private val viewModel: ShortcutHelperViewModel,
) : ComponentActivity() {
@@ -79,13 +83,16 @@
private fun setUpComposeView() {
requireViewById<ComposeView>(R.id.shortcut_helper_compose_container).apply {
setContent {
- PlatformTheme {
- val shortcutsUiState by viewModel.shortcutsUiState.collectAsStateWithLifecycle()
- ShortcutHelper(
- shortcutsUiState = shortcutsUiState,
- onKeyboardSettingsClicked = ::onKeyboardSettingsClicked,
- onSearchQueryChanged = { viewModel.onSearchQueryChanged(it) },
- )
+ CompositionLocalProvider(LocalContext provides userTracker.userContext) {
+ PlatformTheme {
+ val shortcutsUiState by
+ viewModel.shortcutsUiState.collectAsStateWithLifecycle()
+ ShortcutHelper(
+ shortcutsUiState = shortcutsUiState,
+ onKeyboardSettingsClicked = ::onKeyboardSettingsClicked,
+ onSearchQueryChanged = { viewModel.onSearchQueryChanged(it) },
+ )
+ }
}
}
}
@@ -93,7 +100,10 @@
private fun onKeyboardSettingsClicked() {
try {
- startActivity(Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS))
+ startActivityAsUser(
+ Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS),
+ userTracker.userHandle
+ )
} catch (e: ActivityNotFoundException) {
// From the Settings docs: In some cases, a matching Activity may not exist, so ensure
// you safeguard against this.
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index e64cc80..19b46e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyboard.shortcut.ui.viewmodel
+import android.app.role.RoleManager
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
@@ -25,6 +26,7 @@
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.CurrentApp
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
+import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -35,10 +37,13 @@
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
class ShortcutHelperViewModel
@Inject
constructor(
+ private val roleManager: RoleManager,
+ private val userTracker: UserTracker,
@Background private val backgroundScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val stateInteractor: ShortcutHelperStateInteractor,
@@ -72,13 +77,22 @@
initialValue = ShortcutsUiState.Inactive
)
- private fun getDefaultSelectedCategory(
+ private suspend fun getDefaultSelectedCategory(
categories: List<ShortcutCategory>
): ShortcutCategoryType? {
- val currentAppShortcuts = categories.firstOrNull { it.type is CurrentApp }
+ val currentAppShortcuts =
+ categories.firstOrNull { it.type is CurrentApp && !isAppLauncher(it.type.packageName) }
return currentAppShortcuts?.type ?: categories.firstOrNull()?.type
}
+ private suspend fun isAppLauncher(packageName: String): Boolean {
+ return withContext(backgroundDispatcher) {
+ roleManager
+ .getRoleHoldersAsUser(RoleManager.ROLE_HOME, userTracker.userHandle)
+ .firstOrNull() == packageName
+ }
+ }
+
private fun filterCategoriesBySearchQuery(
query: String,
categories: List<ShortcutCategory>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index d1a8463..2f41c0b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -62,21 +62,21 @@
const val TAG = "KeyguardUnlock"
/**
- * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating
- * in during keyguard exit.
+ * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating in
+ * during keyguard exit.
*/
const val SURFACE_BEHIND_START_SCALE_FACTOR = 0.95f
/**
- * How much to translate the surface behind the keyguard at the beginning of the exit animation,
- * in terms of percentage of the surface's height.
+ * How much to translate the surface behind the keyguard at the beginning of the exit animation, in
+ * terms of percentage of the surface's height.
*/
const val SURFACE_BEHIND_START_TRANSLATION_Y = 0.05f
/**
- * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This
- * is expressed as percentage of the surface's height, so 0.66f means the surface will scale up
- * from the point at (width / 2, height * 0.66).
+ * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This is
+ * expressed as percentage of the surface's height, so 0.66f means the surface will scale up from
+ * the point at (width / 2, height * 0.66).
*/
const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f
@@ -155,19 +155,20 @@
* [notifyStartSurfaceBehindRemoteAnimation] by [KeyguardViewMediator].
*/
@SysUISingleton
-class KeyguardUnlockAnimationController @Inject constructor(
- private val windowManager: WindowManager,
- @Main private val resources: Resources,
- private val keyguardStateController: KeyguardStateController,
- private val
- keyguardViewMediator: Lazy<KeyguardViewMediator>,
- private val keyguardViewController: KeyguardViewController,
- private val featureFlags: FeatureFlags,
- private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
- private val statusBarStateController: SysuiStatusBarStateController,
- private val notificationShadeWindowController: NotificationShadeWindowController,
- private val powerManager: PowerManager,
- private val wallpaperManager: WallpaperManager,
+open class KeyguardUnlockAnimationController
+@Inject
+constructor(
+ private val windowManager: WindowManager,
+ @Main private val resources: Resources,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardViewMediator: Lazy<KeyguardViewMediator>,
+ private val keyguardViewController: KeyguardViewController,
+ private val featureFlags: FeatureFlags,
+ private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val notificationShadeWindowController: NotificationShadeWindowController,
+ private val powerManager: PowerManager,
+ private val wallpaperManager: WallpaperManager,
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -221,8 +222,8 @@
var playingCannedUnlockAnimation = false
/**
- * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once
- * and should ignore any future changes to the dismiss amount before the animation finishes.
+ * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once and
+ * should ignore any future changes to the dismiss amount before the animation finishes.
*/
var dismissAmountThresholdsReached = false
@@ -235,9 +236,7 @@
*/
private var launcherUnlockController: ILauncherUnlockAnimationController? = null
- /**
- * Fully qualified class name of the launcher activity
- */
+ /** Fully qualified class name of the launcher activity */
private var launcherActivityClass: String? = null
private val listeners = ArrayList<KeyguardUnlockAnimationListener>()
@@ -248,8 +247,8 @@
* transition, but that's okay!
*/
override fun setLauncherUnlockController(
- activityClass: String,
- callback: ILauncherUnlockAnimationController?
+ activityClass: String,
+ callback: ILauncherUnlockAnimationController?
) {
launcherActivityClass = activityClass
launcherUnlockController = callback
@@ -274,8 +273,7 @@
* If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned
* animation is started in [playCannedUnlockAnimation].
*/
- @VisibleForTesting
- var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
+ @VisibleForTesting var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
private var surfaceBehindRemoteAnimationTargets: Array<RemoteAnimationTarget>? = null
private var openingWallpaperTargets: Array<RemoteAnimationTarget>? = null
private var closingWallpaperTargets: Array<RemoteAnimationTarget>? = null
@@ -291,8 +289,7 @@
*/
private var surfaceBehindAlpha = 1f
- @VisibleForTesting
- var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
+ @VisibleForTesting var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
var wallpaperCannedUnlockAnimator = ValueAnimator.ofFloat(0f, 1f)
@@ -310,8 +307,7 @@
* Animator that animates in the surface behind the keyguard. This is used to play a canned
* animation on the surface, if we're not doing a swipe gesture.
*/
- @VisibleForTesting
- val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
+ @VisibleForTesting val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
/** Rounded corner radius to apply to the surface behind the keyguard. */
private var roundedCornerRadius = 0f
@@ -322,8 +318,7 @@
* window like any other app. This can be true while [willUnlockWithSmartspaceTransition] is
* false, if the smartspace is not available or was not ready in time.
*/
- @VisibleForTesting
- var willUnlockWithInWindowLauncherAnimations: Boolean = false
+ @VisibleForTesting var willUnlockWithInWindowLauncherAnimations: Boolean = false
/**
* Whether we called [ILauncherUnlockAnimationController.prepareForUnlock], but have not yet
@@ -353,49 +348,64 @@
surfaceBehindAlpha = valueAnimator.animatedValue as Float
updateSurfaceBehindAppearAmount()
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- // If we animated the surface alpha to 0f, it means we cancelled a swipe to
- // dismiss. In this case, we should ask the KeyguardViewMediator to end the
- // remote animation to hide the surface behind the keyguard, but should *not*
- // call onKeyguardExitRemoteAnimationFinished since that will hide the keyguard
- // and unlock the device as well as hiding the surface.
- if (surfaceBehindAlpha == 0f) {
- Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
- surfaceBehindRemoteAnimationTargets = null
- openingWallpaperTargets = null
- closingWallpaperTargets = null
- keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- } else {
- Log.d(TAG, "skip finishSurfaceBehindRemoteAnimation" +
- " surfaceBehindAlpha=$surfaceBehindAlpha")
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ // If we animated the surface alpha to 0f, it means we cancelled a swipe to
+ // dismiss. In this case, we should ask the KeyguardViewMediator to end the
+ // remote animation to hide the surface behind the keyguard, but should
+ // *not* call onKeyguardExitRemoteAnimationFinished since that will hide the
+ // keyguard and unlock the device as well as hiding the surface.
+ if (surfaceBehindAlpha == 0f) {
+ Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
+ surfaceBehindRemoteAnimationTargets = null
+ openingWallpaperTargets = null
+ closingWallpaperTargets = null
+ keyguardViewMediator
+ .get()
+ .finishSurfaceBehindRemoteAnimation(false /* cancelled */)
+ } else {
+ Log.d(
+ TAG,
+ "skip finishSurfaceBehindRemoteAnimation" +
+ " surfaceBehindAlpha=$surfaceBehindAlpha"
+ )
+ }
}
}
- })
+ )
}
with(wallpaperCannedUnlockAnimator) {
- duration = if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
- else LAUNCHER_ICONS_ANIMATION_DURATION_MS
- interpolator = if (fasterUnlockTransition()) Interpolators.LINEAR
- else Interpolators.ALPHA_OUT
+ duration =
+ if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
+ else LAUNCHER_ICONS_ANIMATION_DURATION_MS
+ interpolator =
+ if (fasterUnlockTransition()) Interpolators.LINEAR else Interpolators.ALPHA_OUT
addUpdateListener { valueAnimator: ValueAnimator ->
setWallpaperAppearAmount(
- valueAnimator.animatedValue as Float, openingWallpaperTargets)
+ valueAnimator.animatedValue as Float,
+ openingWallpaperTargets
+ )
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator) {
- super.onAnimationStart(animation)
- Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ super.onAnimationStart(animation)
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+ false /* cancelled */
+ )
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
+ }
}
- override fun onAnimationEnd(animation: Animator) {
- Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- Trace.asyncTraceEnd(Trace.TRACE_TAG_APP, "WallpaperAlphaAnimation", 0)
- }
- })
+ )
}
if (fasterUnlockTransition()) {
@@ -405,7 +415,9 @@
interpolator = Interpolators.LINEAR
addUpdateListener { valueAnimator: ValueAnimator ->
setWallpaperAppearAmount(
- valueAnimator.animatedValue as Float, closingWallpaperTargets)
+ valueAnimator.animatedValue as Float,
+ closingWallpaperTargets
+ )
}
}
}
@@ -418,15 +430,19 @@
surfaceBehindAlpha = valueAnimator.animatedValue as Float
setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float)
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- Log.d(TAG, "surfaceBehindEntryAnimator#onAnimationEnd")
- playingCannedUnlockAnimation = false
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */
- )
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ Log.d(TAG, "surfaceBehindEntryAnimator#onAnimationEnd")
+ playingCannedUnlockAnimation = false
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+ false /* cancelled */
+ )
+ }
}
- })
+ )
}
// Listen for changes in the dismiss amount.
@@ -436,9 +452,7 @@
resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
}
- /**
- * Add a listener to be notified of various stages of the unlock animation.
- */
+ /** Add a listener to be notified of various stages of the unlock animation. */
fun addKeyguardUnlockAnimationListener(listener: KeyguardUnlockAnimationListener) {
listeners.add(listener)
}
@@ -454,11 +468,11 @@
fun canPerformInWindowLauncherAnimations(): Boolean {
// TODO(b/278086361): Refactor in-window animations.
return !KeyguardWmStateRefactor.isEnabled &&
- isSupportedLauncherUnderneath() &&
- // If the launcher is underneath, but we're about to launch an activity, don't do
- // the animations since they won't be visible.
- !notificationShadeWindowController.isLaunchingActivity &&
- launcherUnlockController != null
+ isSupportedLauncherUnderneath() &&
+ // If the launcher is underneath, but we're about to launch an activity, don't do
+ // the animations since they won't be visible.
+ !notificationShadeWindowController.isLaunchingActivity &&
+ launcherUnlockController != null
}
/**
@@ -469,8 +483,11 @@
private fun logInWindowAnimationConditions() {
Log.wtf(TAG, "canPerformInWindowLauncherAnimations expected all of these to be true: ")
Log.wtf(TAG, " isNexusLauncherUnderneath: ${isSupportedLauncherUnderneath()}")
- Log.wtf(TAG, " !notificationShadeWindowController.isLaunchingActivity: " +
- "${!notificationShadeWindowController.isLaunchingActivity}")
+ Log.wtf(
+ TAG,
+ " !notificationShadeWindowController.isLaunchingActivity: " +
+ "${!notificationShadeWindowController.isLaunchingActivity}"
+ )
Log.wtf(TAG, " launcherUnlockController != null: ${launcherUnlockController != null}")
Log.wtf(TAG, " !isFoldable(context): ${!isFoldable(resources)}")
}
@@ -480,8 +497,10 @@
* changed.
*/
override fun onKeyguardGoingAwayChanged() {
- if (keyguardStateController.isKeyguardGoingAway &&
- !statusBarStateController.leaveOpenOnKeyguardHide()) {
+ if (
+ keyguardStateController.isKeyguardGoingAway &&
+ !statusBarStateController.leaveOpenOnKeyguardHide()
+ ) {
prepareForInWindowLauncherAnimations()
}
@@ -489,16 +508,22 @@
// make sure that we've left the launcher at 100% unlocked. This is a fail-safe to prevent
// against "tiny launcher" and similar states where the launcher is left in the prepared to
// animate state.
- if (!keyguardStateController.isKeyguardGoingAway &&
- willUnlockWithInWindowLauncherAnimations) {
+ if (
+ !keyguardStateController.isKeyguardGoingAway && willUnlockWithInWindowLauncherAnimations
+ ) {
try {
- launcherUnlockController?.setUnlockAmount(1f,
- biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */)
+ launcherUnlockController?.setUnlockAmount(
+ 1f,
+ biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */
+ )
} catch (e: DeadObjectException) {
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null in " +
"onKeyguardGoingAwayChanged(). Catching exception as this should mean " +
"Launcher is in the process of being destroyed, but the IPC to System UI " +
- "telling us hasn't arrived yet.")
+ "telling us hasn't arrived yet."
+ )
}
}
}
@@ -525,22 +550,26 @@
// Grab the bounds of our lockscreen smartspace and send them to launcher so they can
// position their smartspace there initially, then animate it to its resting position.
if (willUnlockWithSmartspaceTransition) {
- lockscreenSmartspaceBounds = Rect().apply {
- lockscreenSmartspace!!.getBoundsOnScreen(this)
+ lockscreenSmartspaceBounds =
+ Rect().apply {
+ lockscreenSmartspace!!.getBoundsOnScreen(this)
- // The smartspace container on the lockscreen has left and top padding to align it
- // with other lockscreen content. This padding is inside the bounds on screen, so
- // add it to those bounds so that the padding-less launcher smartspace is properly
- // aligned.
- offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop)
+ // The smartspace container on the lockscreen has left and top padding to align
+ // it with other lockscreen content. This padding is inside the bounds on
+ // screen, so add it to those bounds so that the padding-less launcher
+ // smartspace is properly aligned.
+ offset(lockscreenSmartspace!!.paddingLeft, lockscreenSmartspace!!.paddingTop)
- // Also offset by the current card's top padding, if it has any. This allows us to
- // align the tops of the lockscreen/launcher smartspace cards. Some cards, such as
- // the three-line date/weather/alarm card, only have three lines on lockscreen but
- // two on launcher.
- offset(0, (lockscreenSmartspace
- as? BcSmartspaceDataPlugin.SmartspaceView)?.currentCardTopPadding ?: 0)
- }
+ // Also offset by the current card's top padding, if it has any. This allows us
+ // to align the tops of the lockscreen/launcher smartspace cards. Some cards,
+ // such as the three-line date/weather/alarm card, only have three lines on
+ // lockscreen but two on launcher.
+ offset(
+ 0,
+ (lockscreenSmartspace as? BcSmartspaceDataPlugin.SmartspaceView)
+ ?.currentCardTopPadding ?: 0
+ )
+ }
}
// Currently selected lockscreen smartspace page, or -1 if it's not available.
@@ -583,8 +612,8 @@
requestedShowSurfaceBehindKeyguard: Boolean
) {
if (surfaceTransactionApplier == null) {
- surfaceTransactionApplier = SyncRtSurfaceTransactionApplier(
- keyguardViewController.viewRootImpl.view)
+ surfaceTransactionApplier =
+ SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
}
surfaceBehindRemoteAnimationTargets = targets
@@ -603,8 +632,10 @@
// surface behind the keyguard to finish unlocking.
if (keyguardStateController.isFlingingToDismissKeyguard) {
playCannedUnlockAnimation()
- } else if (keyguardStateController.isDismissingFromSwipe &&
- willUnlockWithInWindowLauncherAnimations) {
+ } else if (
+ keyguardStateController.isDismissingFromSwipe &&
+ willUnlockWithInWindowLauncherAnimations
+ ) {
// If we're swiping to unlock to the Launcher, and can play in-window animations,
// make the launcher surface fully visible and play the in-window unlock animation
// on the launcher icons. System UI will remain locked, using the swipe-to-unlock
@@ -615,19 +646,23 @@
try {
launcherUnlockController?.playUnlockAnimation(
- true,
- unlockAnimationDurationMs() + cannedUnlockStartDelayMs(),
- 0 /* startDelay */)
+ true,
+ unlockAnimationDurationMs() + cannedUnlockStartDelayMs(),
+ 0 /* startDelay */
+ )
} catch (e: DeadObjectException) {
// Hello! If you are here investigating a bug where Launcher is blank (no icons)
// then the below assumption about Launcher's destruction was incorrect. This
// would mean prepareToUnlock was called (blanking Launcher in preparation for
// the beginning of the unlock animation), but then somehow we were unable to
// call playUnlockAnimation to animate the icons back in.
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null. " +
"Catching exception as this should mean Launcher is in the process " +
"of being destroyed, but the IPC to System UI telling us hasn't " +
- "arrived yet.")
+ "arrived yet."
+ )
}
launcherPreparedForUnlock = false
@@ -643,15 +678,18 @@
}
// Notify if waking from AOD only
- val isWakeAndUnlockNotFromDream = biometricUnlockControllerLazy.get().isWakeAndUnlock &&
- biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
+ val isWakeAndUnlockNotFromDream =
+ biometricUnlockControllerLazy.get().isWakeAndUnlock &&
+ biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
listeners.forEach {
it.onUnlockAnimationStarted(
playingCannedUnlockAnimation /* playingCannedAnimation */,
isWakeAndUnlockNotFromDream /* isWakeAndUnlockNotFromDream */,
cannedUnlockStartDelayMs() /* unlockStartDelay */,
- LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) }
+ LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */
+ )
+ }
// Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
// Check it here in case there is no more change to the dismiss amount after the last change
@@ -685,8 +723,9 @@
biometricUnlockControllerLazy.get().isWakeAndUnlock -> {
Log.d(TAG, "playCannedUnlockAnimation, isWakeAndUnlock")
setSurfaceBehindAppearAmount(1f)
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
// Otherwise, we're doing a normal full-window unlock. Start this animator, which will
@@ -698,8 +737,11 @@
}
if (launcherPreparedForUnlock && !willUnlockWithInWindowLauncherAnimations) {
- Log.wtf(TAG, "Launcher is prepared for unlock, so we should have started the " +
- "in-window animation, however we apparently did not.")
+ Log.wtf(
+ TAG,
+ "Launcher is prepared for unlock, so we should have started the " +
+ "in-window animation, however we apparently did not."
+ )
logInWindowAnimationConditions()
}
}
@@ -708,7 +750,6 @@
* Unlock to the launcher, using in-window animations, and the smartspace shared element
* transition if possible.
*/
-
@VisibleForTesting
fun unlockToLauncherWithInWindowAnimations() {
surfaceBehindAlpha = 1f
@@ -717,26 +758,32 @@
try {
// Begin the animation, waiting for the shade to animate out.
launcherUnlockController?.playUnlockAnimation(
- true /* unlocked */,
- LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
- cannedUnlockStartDelayMs() /* startDelay */)
+ true /* unlocked */,
+ LAUNCHER_ICONS_ANIMATION_DURATION_MS /* duration */,
+ cannedUnlockStartDelayMs() /* startDelay */
+ )
} catch (e: DeadObjectException) {
// Hello! If you are here investigating a bug where Launcher is blank (no icons)
// then the below assumption about Launcher's destruction was incorrect. This
// would mean prepareToUnlock was called (blanking Launcher in preparation for
// the beginning of the unlock animation), but then somehow we were unable to
// call playUnlockAnimation to animate the icons back in.
- Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null. " +
+ Log.e(
+ TAG,
+ "launcherUnlockAnimationController was dead, but non-null. " +
"Catching exception as this should mean Launcher is in the process " +
"of being destroyed, but the IPC to System UI telling us hasn't " +
- "arrived yet.")
+ "arrived yet."
+ )
}
launcherPreparedForUnlock = false
// Now that the Launcher surface (with its smartspace positioned identically to ours) is
// visible, hide our smartspace.
- if (lockscreenSmartspace?.visibility == View.VISIBLE) {
+ if (
+ shouldPerformSmartspaceTransition() && lockscreenSmartspace?.visibility == View.VISIBLE
+ ) {
lockscreenSmartspace?.visibility = View.INVISIBLE
}
@@ -747,22 +794,31 @@
fadeOutWallpaper()
}
- handler.postDelayed({
- if (keyguardViewMediator.get().isShowingAndNotOccluded &&
- !keyguardStateController.isKeyguardGoingAway) {
- Log.e(TAG, "Finish keyguard exit animation delayed Runnable ran, but we are " +
- "showing and not going away.")
- return@postDelayed
- }
+ handler.postDelayed(
+ {
+ if (
+ keyguardViewMediator.get().isShowingAndNotOccluded &&
+ !keyguardStateController.isKeyguardGoingAway
+ ) {
+ Log.e(
+ TAG,
+ "Finish keyguard exit animation delayed Runnable ran, but we are " +
+ "showing and not going away."
+ )
+ return@postDelayed
+ }
- if (openingWallpaperTargets?.isNotEmpty() == true) {
- fadeInWallpaper()
- hideKeyguardViewAfterRemoteAnimation()
- } else {
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- }
- }, cannedUnlockStartDelayMs())
+ if (openingWallpaperTargets?.isNotEmpty() == true) {
+ fadeInWallpaper()
+ hideKeyguardViewAfterRemoteAnimation()
+ } else {
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
+ }
+ },
+ cannedUnlockStartDelayMs()
+ )
}
/**
@@ -784,12 +840,14 @@
// interaction tight.
if (keyguardStateController.isFlingingToDismissKeyguard) {
setSurfaceBehindAppearAmount(keyguardStateController.dismissAmount)
- } else if (keyguardStateController.isDismissingFromSwipe ||
- keyguardStateController.isSnappingKeyguardBackAfterSwipe) {
+ } else if (
+ keyguardStateController.isDismissingFromSwipe ||
+ keyguardStateController.isSnappingKeyguardBackAfterSwipe
+ ) {
val totalSwipeDistanceToDismiss =
- (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD)
+ (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD)
val swipedDistanceSoFar: Float =
- keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD
+ keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD
val progress = swipedDistanceSoFar / totalSwipeDistanceToDismiss
setSurfaceBehindAppearAmount(progress)
}
@@ -801,10 +859,13 @@
// If the surface is visible or it's about to be, start updating its appearance to
// reflect the new dismiss amount.
- if ((keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
- keyguardViewMediator.get()
+ if (
+ (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+ keyguardViewMediator
+ .get()
.isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) &&
- !playingCannedUnlockAnimation) {
+ !playingCannedUnlockAnimation
+ ) {
updateSurfaceBehindAppearAmount()
}
}
@@ -838,11 +899,15 @@
val dismissAmount = keyguardStateController.dismissAmount
- if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
- !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
+ if (
+ dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
+ !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()
+ ) {
keyguardViewMediator.get().showSurfaceBehindKeyguard()
- } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
- keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
+ } else if (
+ dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
+ keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()
+ ) {
// We're no longer past the threshold but we are showing the surface. Animate it
// out.
keyguardViewMediator.get().hideSurfaceBehindKeyguard()
@@ -868,22 +933,27 @@
}
// no-op if animation is not requested yet.
- if (!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
- !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
+ if (
+ !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
+ !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe
+ ) {
return
}
val dismissAmount = keyguardStateController.dismissAmount
- if (dismissAmount >= 1f ||
+ if (
+ dismissAmount >= 1f ||
(keyguardStateController.isDismissingFromSwipe &&
- // Don't hide if we're flinging during a swipe, since we need to finish
- // animating it out. This will be called again after the fling ends.
- !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
- dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) {
+ // Don't hide if we're flinging during a swipe, since we need to finish
+ // animating it out. This will be called again after the fling ends.
+ !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
+ dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)
+ ) {
setSurfaceBehindAppearAmount(1f)
dismissAmountThresholdsReached = true
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ keyguardViewMediator
+ .get()
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
}
@@ -894,51 +964,56 @@
* wallpapers, this transitions between the two wallpapers
*/
fun setSurfaceBehindAppearAmount(amount: Float, wallpapers: Boolean = true) {
- val animationAlpha = when {
- // If we're snapping the keyguard back, immediately begin fading it out.
- keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
- // If the screen has turned back off, the unlock animation is going to be cancelled,
- // so set the surface alpha to 0f so it's no longer visible.
- !powerManager.isInteractive -> 0f
- else -> surfaceBehindAlpha
- }
+ val animationAlpha =
+ when {
+ // If we're snapping the keyguard back, immediately begin fading it out.
+ keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
+ // If the screen has turned back off, the unlock animation is going to be cancelled,
+ // so set the surface alpha to 0f so it's no longer visible.
+ !powerManager.isInteractive -> 0f
+ else -> surfaceBehindAlpha
+ }
surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
if (!KeyguardWmStateRefactor.isEnabled) {
val surfaceHeight: Int =
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
- var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
- (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
- MathUtils.clamp(amount, 0f, 1f))
+ var scaleFactor =
+ (SURFACE_BEHIND_START_SCALE_FACTOR +
+ (1f - SURFACE_BEHIND_START_SCALE_FACTOR) * MathUtils.clamp(amount, 0f, 1f))
// If we're dismissing via swipe to the Launcher, we'll play in-window scale
// animations, so don't also scale the window.
- if (keyguardStateController.isDismissingFromSwipe &&
- willUnlockWithInWindowLauncherAnimations) {
+ if (
+ keyguardStateController.isDismissingFromSwipe &&
+ willUnlockWithInWindowLauncherAnimations
+ ) {
scaleFactor = 1f
}
// Translate up from the bottom.
surfaceBehindMatrix.setTranslate(
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
- surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+ surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
)
// Scale up from a point at the center-bottom of the surface.
surfaceBehindMatrix.postScale(
- scaleFactor,
- scaleFactor,
- keyguardViewController.viewRootImpl.width / 2f,
- surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+ scaleFactor,
+ scaleFactor,
+ keyguardViewController.viewRootImpl.width / 2f,
+ surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
)
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
- if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
- sc?.isValid == true) {
+ if (
+ keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true
+ ) {
with(SurfaceControl.Transaction()) {
setMatrix(sc, surfaceBehindMatrix, tmpFloat)
setCornerRadius(sc, roundedCornerRadius)
@@ -947,12 +1022,13 @@
}
} else {
applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget.leash)
- .withMatrix(surfaceBehindMatrix)
- .withCornerRadius(roundedCornerRadius)
- .withAlpha(animationAlpha)
- .build()
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget.leash
+ )
+ .withMatrix(surfaceBehindMatrix)
+ .withCornerRadius(roundedCornerRadius)
+ .withAlpha(animationAlpha)
+ .build()
)
}
}
@@ -969,8 +1045,8 @@
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)
+ val fadeOutAmount =
+ ((amount - fadeOutStart) / (fadeOutEnd - fadeOutStart)).coerceIn(0f, 1f)
setWallpaperAppearAmount(fadeInAmount, openingWallpaperTargets)
setWallpaperAppearAmount(1 - fadeOutAmount, closingWallpaperTargets)
@@ -984,18 +1060,19 @@
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = wallpaper.leash
- if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
- sc?.isValid == true) {
+ if (
+ keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true
+ ) {
with(SurfaceControl.Transaction()) {
setAlpha(sc, animationAlpha)
apply()
}
} else {
applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- wallpaper.leash)
- .withAlpha(animationAlpha)
- .build()
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(wallpaper.leash)
+ .withAlpha(animationAlpha)
+ .build()
)
}
}
@@ -1019,9 +1096,9 @@
}
if (!showKeyguard) {
- // Make sure we made the surface behind fully visible, just in case. It should already be
- // fully visible. The exit animation is finished, and we should not hold the leash anymore,
- // so forcing it to 1f.
+ // Make sure we made the surface behind fully visible, just in case. It should already
+ // be fully visible. The exit animation is finished, and we should not hold the leash
+ // anymore, so forcing it to 1f.
surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f)
@@ -1061,13 +1138,16 @@
if (!KeyguardWmStateRefactor.isEnabled) {
keyguardViewController.hide(
- surfaceBehindRemoteAnimationStartTime,
- 0 /* fadeOutDuration */
+ surfaceBehindRemoteAnimationStartTime,
+ 0 /* fadeOutDuration */
)
}
} else {
- Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
- "showing. Ignoring...")
+ Log.i(
+ TAG,
+ "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
+ "showing. Ignoring..."
+ )
}
}
@@ -1099,7 +1179,8 @@
surfaceBehindAlphaAnimator.reverse()
}
- private fun shouldPerformSmartspaceTransition(): Boolean {
+ /** Note: declared open for ease of testing */
+ open fun shouldPerformSmartspaceTransition(): Boolean {
// Feature is disabled, so we don't want to.
if (!featureFlags.isEnabled(Flags.SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED)) {
return false
@@ -1107,9 +1188,11 @@
// If our controllers are null, or we haven't received a smartspace state from Launcher yet,
// we will not be doing any smartspace transitions today.
- if (launcherUnlockController == null ||
- lockscreenSmartspace == null ||
- launcherSmartspaceState == null) {
+ if (
+ launcherUnlockController == null ||
+ lockscreenSmartspace == null ||
+ launcherSmartspaceState == null
+ ) {
return false
}
@@ -1135,8 +1218,10 @@
// element transition is if we're doing a biometric unlock. Otherwise, it means the bouncer
// is showing, and you can't see the lockscreen smartspace, so a shared element transition
// would not make sense.
- if (!keyguardStateController.canDismissLockScreen() &&
- !biometricUnlockControllerLazy.get().isBiometricUnlock) {
+ if (
+ !keyguardStateController.canDismissLockScreen() &&
+ !biometricUnlockControllerLazy.get().isBiometricUnlock
+ ) {
return false
}
@@ -1175,9 +1260,7 @@
return willUnlockWithSmartspaceTransition
}
- /**
- * Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'.
- */
+ /** Whether the RemoteAnimation on the app/launcher surface behind the keyguard is 'running'. */
fun isAnimatingBetweenKeyguardAndSurfaceBehind(): Boolean {
return keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehind
}
@@ -1196,39 +1279,38 @@
* in-window/shared element transitions!
*/
fun isSupportedLauncherUnderneath(): Boolean {
- return launcherActivityClass?.let { ActivityManagerWrapper.getInstance()
- .runningTask?.topActivity?.className?.equals(it) }
- ?: false
+ return launcherActivityClass?.let {
+ ActivityManagerWrapper.getInstance().runningTask?.topActivity?.className?.equals(it)
+ } ?: false
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+ * itself when flag is removed
*/
private fun cannedUnlockStartDelayMs(): Long {
return if (fasterUnlockTransition()) CANNED_UNLOCK_START_DELAY
- else LEGACY_CANNED_UNLOCK_START_DELAY
+ else LEGACY_CANNED_UNLOCK_START_DELAY
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * Temporary method for b/298186160 TODO (b/298186160) replace references with the constant
+ * itself when flag is removed
*/
private fun unlockAnimationDurationMs(): Long {
return if (fasterUnlockTransition()) UNLOCK_ANIMATION_DURATION_MS
- else LEGACY_UNLOCK_ANIMATION_DURATION_MS
+ else LEGACY_UNLOCK_ANIMATION_DURATION_MS
}
/**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
+ * 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()) UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
- else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
+ else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
}
-
companion object {
fun isFoldable(resources: Resources): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index ae751db..edf17c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -34,6 +34,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
@@ -237,6 +238,9 @@
/** Observable updated when keyguardDone should be called either now or soon. */
val keyguardDone: Flow<KeyguardDone>
+ /** Last camera launch detection event */
+ val onCameraLaunchDetected: MutableStateFlow<CameraLaunchSourceModel>
+
/**
* Emits after the keyguard is done animating away.
*
@@ -380,6 +384,8 @@
private val _keyguardAlpha = MutableStateFlow(1f)
override val keyguardAlpha = _keyguardAlpha.asStateFlow()
+ override val onCameraLaunchDetected = MutableStateFlow(CameraLaunchSourceModel())
+
override val panelAlpha: MutableStateFlow<Float> = MutableStateFlow(1f)
private val _clockShouldBeCentered = MutableStateFlow(true)
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 046e79c..42490c4 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
@@ -23,10 +23,7 @@
import android.graphics.Point
import android.util.MathUtils
import com.android.app.animation.Interpolators
-import com.android.app.tracing.FlowTracing.tracedAwaitClose
-import com.android.app.tracing.FlowTracing.tracedConflatedCallbackFlow
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -35,9 +32,11 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.keyguard.shared.model.CameraLaunchType
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -48,11 +47,8 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
-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.pairwise
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import javax.inject.Provider
@@ -69,9 +65,7 @@
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -87,7 +81,6 @@
@Inject
constructor(
private val repository: KeyguardRepository,
- private val commandQueue: CommandQueue,
powerInteractor: PowerInteractor,
bouncerRepository: KeyguardBouncerRepository,
configurationInteractor: ConfigurationInteractor,
@@ -102,55 +95,34 @@
// TODO(b/296118689): move to a repository
private val _notificationPlaceholderBounds = MutableStateFlow(NotificationContainerBounds())
- // When going to AOD, we interpolate bounds when receiving the new bounds
- // When going back to LS, we'll apply new bounds directly
- private val _nonSplitShadeNotifciationPlaceholderBounds =
- _notificationPlaceholderBounds.pairwise().flatMapLatest { (oldBounds, newBounds) ->
- val lastChangeStep = keyguardTransitionInteractor.transitionState.first()
- if (lastChangeStep.to == AOD) {
- keyguardTransitionInteractor.transitionState.map { step ->
- val startingProgress = lastChangeStep.value
- val progress = step.value
- if (step.to == AOD && progress >= startingProgress && startingProgress < 1f) {
- val adjustedProgress =
- ((progress - startingProgress) / (1F - startingProgress)).coerceIn(
- 0F,
- 1F
- )
- val top = interpolate(oldBounds.top, newBounds.top, adjustedProgress)
- val bottom =
- interpolate(
- oldBounds.bottom,
- newBounds.bottom,
- adjustedProgress.coerceIn(0F, 1F)
- )
- NotificationContainerBounds(top = top, bottom = bottom)
- } else {
- newBounds
- }
- }
- } else {
- flow { emit(newBounds) }
- }
- }
-
/** Bounds of the notification container. */
val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy {
SceneContainerFlag.assertInLegacyMode()
- combine(
+ combineTransform(
_notificationPlaceholderBounds,
- _nonSplitShadeNotifciationPlaceholderBounds,
sharedNotificationContainerInteractor.get().configurationBasedDimensions,
- ) { bounds, nonSplitShadeBounds: NotificationContainerBounds, cfg ->
+ keyguardTransitionInteractor.isInTransition(
+ edge = Edge.create(from = LOCKSCREEN, to = AOD)
+ ),
+ ) { bounds, cfg, isTransitioningToAod ->
+ if (isTransitioningToAod) {
+ // Keep bounds stable during this transition, to prevent cases like smartspace
+ // popping in and adjusting the bounds. A prime example would be media playing,
+ // which then updates smartspace on transition to AOD
+ return@combineTransform
+ }
+
// We offset the placeholder bounds by the configured top margin to account for
// legacy placement behavior within notifications for splitshade.
- if (MigrateClocksToBlueprint.isEnabled) {
- if (cfg.useSplitShade) {
- bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin)
- } else {
- nonSplitShadeBounds
- }
- } else bounds
+ emit(
+ if (MigrateClocksToBlueprint.isEnabled) {
+ if (cfg.useSplitShade) {
+ bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin)
+ } else {
+ bounds
+ }
+ } else bounds
+ )
}
.stateIn(
scope = applicationScope,
@@ -198,22 +170,7 @@
/** Event for when the camera gesture is detected */
val onCameraLaunchDetected: Flow<CameraLaunchSourceModel> =
- tracedConflatedCallbackFlow("KeyguardInteractor#onCameraLaunchDetected") {
- val callback =
- object : CommandQueue.Callbacks {
- override fun onCameraLaunchGestureDetected(source: Int) {
- trySendWithFailureLogging(
- cameraLaunchSourceIntToModel(source),
- TAG,
- "updated onCameraLaunchGestureDetected"
- )
- }
- }
-
- commandQueue.addCallback(callback)
-
- tracedAwaitClose("onCameraLaunchDetected") { commandQueue.removeCallback(callback) }
- }
+ repository.onCameraLaunchDetected.filter { it.type != CameraLaunchType.IGNORE }
/**
* Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
@@ -310,7 +267,7 @@
when {
isKeyguardVisible -> false
isPrimaryBouncerShowing -> false
- else -> cameraLaunchEvent == CameraLaunchSourceModel.POWER_DOUBLE_TAP
+ else -> cameraLaunchEvent.type == CameraLaunchType.POWER_DOUBLE_TAP
}
}
.onStart { emit(false) }
@@ -440,16 +397,15 @@
return repository.isKeyguardShowing()
}
- private fun cameraLaunchSourceIntToModel(value: Int): CameraLaunchSourceModel {
+ private fun cameraLaunchSourceIntToType(value: Int): CameraLaunchType {
return when (value) {
- StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE -> CameraLaunchSourceModel.WIGGLE
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE -> CameraLaunchType.WIGGLE
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP ->
- CameraLaunchSourceModel.POWER_DOUBLE_TAP
- StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER ->
- CameraLaunchSourceModel.LIFT_TRIGGER
+ CameraLaunchType.POWER_DOUBLE_TAP
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER -> CameraLaunchType.LIFT_TRIGGER
StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE ->
- CameraLaunchSourceModel.QUICK_AFFORDANCE
- else -> throw IllegalArgumentException("Invalid CameraLaunchSourceModel value: $value")
+ CameraLaunchType.QUICK_AFFORDANCE
+ else -> throw IllegalArgumentException("Invalid CameraLaunchType value: $value")
}
}
@@ -508,6 +464,11 @@
fromLockscreenTransitionInteractor.get().dismissKeyguard()
}
+ fun onCameraLaunchDetected(source: Int) {
+ repository.onCameraLaunchDetected.value =
+ CameraLaunchSourceModel(type = cameraLaunchSourceIntToType(source))
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index ccce3bf..31236a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -25,6 +25,7 @@
import com.android.app.tracing.coroutines.withContext
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
@@ -80,7 +81,8 @@
private val featureFlags: FeatureFlags,
private val repository: Lazy<KeyguardQuickAffordanceRepository>,
private val launchAnimator: DialogTransitionAnimator,
- private val logger: KeyguardQuickAffordancesMetricsLogger,
+ private val logger: KeyguardQuickAffordancesLogger,
+ private val metricsLogger: KeyguardQuickAffordancesMetricsLogger,
private val devicePolicyManager: DevicePolicyManager,
private val dockManager: DockManager,
private val biometricSettingsRepository: BiometricSettingsRepository,
@@ -171,7 +173,8 @@
Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
return
}
- logger.logOnShortcutTriggered(slotId, configKey)
+ logger.logQuickAffordanceTriggered(decodedSlotId, decodedConfigKey)
+ metricsLogger.logOnShortcutTriggered(slotId, configKey)
when (val result = config.onTriggered(expandable)) {
is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
@@ -223,7 +226,8 @@
affordanceIds = selections,
)
- logger.logOnShortcutSelected(slotId, affordanceId)
+ logger.logQuickAffordanceSelected(slotId, affordanceId)
+ metricsLogger.logOnShortcutSelected(slotId, affordanceId)
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt
index 19baf77..c017651 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchSourceModel.kt
@@ -15,14 +15,8 @@
*/
package com.android.systemui.keyguard.shared.model
-/** Camera launch sources */
-enum class CameraLaunchSourceModel {
- /** Device is wiggled */
- WIGGLE,
- /** Power button has been double tapped */
- POWER_DOUBLE_TAP,
- /** Device has been lifted */
- LIFT_TRIGGER,
- /** Quick affordance button has been pressed */
- QUICK_AFFORDANCE,
-}
+/** Camera launch source, with type and time detected */
+data class CameraLaunchSourceModel(
+ val type: CameraLaunchType = CameraLaunchType.IGNORE,
+ val detectedTime: Long = System.currentTimeMillis(),
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchType.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchType.kt
new file mode 100644
index 0000000..984abbb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/CameraLaunchType.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.shared.model
+
+/** Camera launch sources */
+enum class CameraLaunchType {
+ /** Models no value */
+ IGNORE,
+ /** Device is wiggled */
+ WIGGLE,
+ /** Power button has been double tapped */
+ POWER_DOUBLE_TAP,
+ /** Device has been lifted */
+ LIFT_TRIGGER,
+ /** Quick affordance button has been pressed */
+ QUICK_AFFORDANCE,
+}
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 b387855..830ef3b 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
@@ -46,6 +46,7 @@
onSingleTap: () -> Unit,
falsingManager: FalsingManager,
) {
+ view.contentDescription = view.resources.getString(R.string.accessibility_desc_lock_screen)
view.accessibilityHintLongPressAction =
AccessibilityNodeInfo.AccessibilityAction(
AccessibilityNodeInfoCompat.ACTION_LONG_CLICK,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index b9a79dc..162a0d2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -30,6 +30,7 @@
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.settingslib.Utils
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.view.LaunchableImageView
@@ -74,6 +75,7 @@
alpha: Flow<Float>,
falsingManager: FalsingManager?,
vibratorHelper: VibratorHelper?,
+ logger: KeyguardQuickAffordancesLogger,
messageDisplayer: (Int) -> Unit,
): Binding {
val button = view as ImageView
@@ -89,6 +91,7 @@
falsingManager = falsingManager,
messageDisplayer = messageDisplayer,
vibratorHelper = vibratorHelper,
+ logger = logger,
)
}
}
@@ -131,6 +134,7 @@
falsingManager: FalsingManager?,
messageDisplayer: (Int) -> Unit,
vibratorHelper: VibratorHelper?,
+ logger: KeyguardQuickAffordancesLogger,
) {
if (!viewModel.isVisible) {
view.isInvisible = true
@@ -228,6 +232,7 @@
shakeAnimator.start()
vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
+ logger.logQuickAffordanceTapped(viewModel.configKey)
}
view.onLongClickListener =
OnLongClickListener(falsingManager, viewModel, vibratorHelper, onTouchListener)
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 bc5b7b9..6faca1e 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
@@ -50,6 +50,7 @@
import com.android.internal.policy.SystemBarUtils
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -147,6 +148,7 @@
private val defaultShortcutsSection: DefaultShortcutsSection,
private val keyguardClockInteractor: KeyguardClockInteractor,
private val keyguardClockViewModel: KeyguardClockViewModel,
+ private val quickAffordancesLogger: KeyguardQuickAffordancesLogger,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
@@ -462,6 +464,7 @@
alpha = flowOf(1f),
falsingManager = falsingManager,
vibratorHelper = vibratorHelper,
+ logger = quickAffordancesLogger,
) { message ->
indicationController.showTransientIndication(message)
}
@@ -476,6 +479,7 @@
alpha = flowOf(1f),
falsingManager = falsingManager,
vibratorHelper = vibratorHelper,
+ logger = quickAffordancesLogger,
) { message ->
indicationController.showTransientIndication(message)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index 2e96638..1ba830b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -25,6 +25,7 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -47,6 +48,7 @@
private val falsingManager: FalsingManager,
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
+ private val shortcutsLogger: KeyguardQuickAffordancesLogger,
) : BaseShortcutSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (KeyguardBottomAreaRefactor.isEnabled) {
@@ -64,6 +66,7 @@
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
@@ -74,6 +77,7 @@
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 9146c60..64c46db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -26,6 +26,7 @@
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.animation.view.LaunchableImageView
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -52,6 +53,7 @@
private val indicationController: KeyguardIndicationController,
private val vibratorHelper: VibratorHelper,
private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
+ private val shortcutsLogger: KeyguardQuickAffordancesLogger,
) : BaseShortcutSection() {
// Amount to increase the bottom margin by to avoid colliding with inset
@@ -86,6 +88,7 @@
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
@@ -96,6 +99,7 @@
keyguardQuickAffordancesCombinedViewModel.transitionAlpha,
falsingManager,
vibratorHelper,
+ shortcutsLogger,
) {
indicationController.showTransientIndication(it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index 00aa102..ea8fe29 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.LayoutDirection
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -54,7 +55,10 @@
val dreamOverlayTranslationX: Flow<Float> =
configurationInteractor
- .dimensionPixelSize(R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x)
+ .directionalDimensionPixelSize(
+ LayoutDirection.LTR,
+ R.dimen.dreaming_to_hub_transition_dream_overlay_translation_x
+ )
.flatMapLatest { translatePx ->
transitionAnimation.sharedFlow(
duration = TO_GLANCEABLE_HUB_DURATION,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
index d594488..76d5a8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.LayoutDirection
import com.android.app.animation.Interpolators
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -63,7 +64,10 @@
val dreamOverlayTranslationX: Flow<Float> =
configurationInteractor
- .dimensionPixelSize(R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x)
+ .directionalDimensionPixelSize(
+ LayoutDirection.LTR,
+ R.dimen.hub_to_dreaming_transition_dream_overlay_translation_x
+ )
.flatMapLatest { translatePx: Int ->
transitionAnimation.sharedFlow(
duration = FROM_GLANCEABLE_HUB_DURATION,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index 046b95f..67b009e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.LayoutDirection
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -72,7 +73,10 @@
val keyguardTranslationX: Flow<StateToValue> =
configurationInteractor
- .dimensionPixelSize(R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x)
+ .directionalDimensionPixelSize(
+ LayoutDirection.LTR,
+ R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x
+ )
.flatMapLatest { translatePx: Int ->
transitionAnimation.sharedFlowWithState(
duration = TO_LOCKSCREEN_DURATION,
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 630dcca..15892e9 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
@@ -25,7 +25,6 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
@@ -35,93 +34,70 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.util.kotlin.filterValuesNotNull
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the lockscreen scene. */
@SysUISingleton
class LockscreenSceneViewModel
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
- deviceEntryInteractor: DeviceEntryInteractor,
- communalInteractor: CommunalInteractor,
- shadeInteractor: ShadeInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val communalInteractor: CommunalInteractor,
+ private val shadeInteractor: ShadeInteractor,
val touchHandling: KeyguardTouchHandlingViewModel,
val notifications: NotificationsPlaceholderViewModel,
) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- shadeInteractor.isShadeTouchable
- .flatMapLatest { isShadeTouchable ->
- if (!isShadeTouchable) {
- flowOf(emptyMap())
- } else {
- combine(
- deviceEntryInteractor.isUnlocked,
- communalInteractor.isCommunalAvailable,
- shadeInteractor.shadeMode,
- ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
- destinationScenes(
- isDeviceUnlocked = isDeviceUnlocked,
- isCommunalAvailable = isCommunalAvailable,
- shadeMode = shadeMode,
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ shadeInteractor.isShadeTouchable.flatMapLatest { isShadeTouchable ->
+ if (!isShadeTouchable) {
+ flowOf(emptyMap())
+ } else {
+ combine(
+ deviceEntryInteractor.isUnlocked,
+ communalInteractor.isCommunalAvailable,
+ shadeInteractor.shadeMode,
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
+ val notifShadeSceneKey =
+ UserActionResult(
+ toScene = SceneFamilies.NotifShade,
+ transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
)
- }
+
+ mapOf(
+ Swipe.Left to
+ UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
+ Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
+
+ // Swiping down from the top edge goes to QS (or shade if in split shade
+ // mode).
+ swipeDownFromTop(pointerCount = 1) to
+ if (shadeMode is ShadeMode.Single) {
+ UserActionResult(Scenes.QuickSettings)
+ } else {
+ notifShadeSceneKey
+ },
+
+ // TODO(b/338577208): Remove once we add Dual Shade invocation zones.
+ swipeDownFromTop(pointerCount = 2) to
+ UserActionResult(
+ toScene = SceneFamilies.QuickSettings,
+ transitionKey =
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ ),
+
+ // Swiping down, not from the edge, always navigates to the notif shade
+ // scene.
+ swipeDown(pointerCount = 1) to notifShadeSceneKey,
+ swipeDown(pointerCount = 2) to notifShadeSceneKey,
+ )
+ .filterValuesNotNull()
}
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- isDeviceUnlocked = deviceEntryInteractor.isUnlocked.value,
- isCommunalAvailable = false,
- shadeMode = shadeInteractor.shadeMode.value,
- ),
- )
-
- private fun destinationScenes(
- isDeviceUnlocked: Boolean,
- isCommunalAvailable: Boolean,
- shadeMode: ShadeMode,
- ): Map<UserAction, UserActionResult> {
- val notifShadeSceneKey =
- UserActionResult(
- toScene = SceneFamilies.NotifShade,
- transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
- )
-
- return mapOf(
- Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
- Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
-
- // Swiping down from the top edge goes to QS (or shade if in split shade mode).
- swipeDownFromTop(pointerCount = 1) to
- if (shadeMode is ShadeMode.Single) {
- UserActionResult(Scenes.QuickSettings)
- } else {
- notifShadeSceneKey
- },
-
- // TODO(b/338577208): Remove once we add Dual Shade invocation zones.
- swipeDownFromTop(pointerCount = 2) to
- UserActionResult(
- toScene = SceneFamilies.QuickSettings,
- transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- ),
-
- // Swiping down, not from the edge, always navigates to the notif shade scene.
- swipeDown(pointerCount = 1) to notifShadeSceneKey,
- swipeDown(pointerCount = 2) to notifShadeSceneKey,
- )
- .filterValuesNotNull()
- }
+ }
private fun swipeDownFromTop(pointerCount: Int): Swipe {
return Swipe(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index c7273b7..378374e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.LayoutDirection
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -71,7 +72,10 @@
val keyguardTranslationX: Flow<StateToValue> =
configurationInteractor
- .dimensionPixelSize(R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x)
+ .directionalDimensionPixelSize(
+ LayoutDirection.LTR,
+ R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x
+ )
.flatMapLatest { translatePx: Int ->
transitionAnimation.sharedFlowWithState(
duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardQuickAffordancesLog.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardQuickAffordancesLog.kt
index 37c9552..e9cf7e2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyguardQuickAffordancesLog.kt
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.log.dagger
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
+import javax.inject.Qualifier
-val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
+/** A [com.android.systemui.log.LogBuffer] for keyguard quick affordances related stuff. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyguardQuickAffordancesLog
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 b2ba0e1..40bb8e1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -568,6 +568,16 @@
}
/**
+ * Provides a {@link LogBuffer} for keyguard quick affordances-related logs.
+ */
+ @Provides
+ @SysUISingleton
+ @KeyguardQuickAffordancesLog
+ public static LogBuffer provideKeyguardQuickAffordancesLogBuffer(LogBufferFactory factory) {
+ return factory.create("KeyguardQuickAffordancesLog", 25);
+ }
+
+ /**
* Provides a {@link LogBuffer} for keyguard transition animation logs.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 6c53374..cc4a92c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -187,70 +187,40 @@
}
}
- CharSequence dialogText = null;
- CharSequence dialogTitle = null;
-
final String appName = extractAppName(aInfo, packageManager);
final boolean hasCastingCapabilities =
Utils.isHeadlessRemoteDisplayProvider(packageManager, mPackageName);
- if (hasCastingCapabilities) {
- dialogText = getString(R.string.media_projection_sys_service_dialog_warning);
- dialogTitle = getString(R.string.media_projection_sys_service_dialog_title);
- } else {
- String actionText = getString(R.string.media_projection_dialog_warning, appName);
- SpannableString message = new SpannableString(actionText);
-
- int appNameIndex = actionText.indexOf(appName);
- if (appNameIndex >= 0) {
- message.setSpan(new StyleSpan(Typeface.BOLD),
- appNameIndex, appNameIndex + appName.length(), 0);
- }
- dialogText = message;
- dialogTitle = getString(R.string.media_projection_dialog_title, appName);
- }
-
// Using application context for the dialog, instead of the activity context, so we get
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
- if (isPartialScreenSharingEnabled()) {
- final boolean overrideDisableSingleAppOption =
- CompatChanges.isChangeEnabled(
- OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
- mPackageName, getHostUserHandle());
- MediaProjectionPermissionDialogDelegate delegate =
- new MediaProjectionPermissionDialogDelegate(
- dialogContext,
- getMediaProjectionConfig(),
- dialog -> {
- ScreenShareOption selectedOption =
- dialog.getSelectedScreenShareOption();
- grantMediaProjectionPermission(selectedOption.getMode());
- },
- () -> finish(RECORD_CANCEL, /* projection= */ null),
- hasCastingCapabilities,
- appName,
- overrideDisableSingleAppOption,
- mUid,
- mMediaProjectionMetricsLogger);
- mDialog =
- new AlertDialogWithDelegate(
- dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
- } else {
- AlertDialog.Builder dialogBuilder =
- new AlertDialog.Builder(dialogContext, R.style.Theme_SystemUI_Dialog)
- .setTitle(dialogTitle)
- .setIcon(R.drawable.ic_media_projection_permission)
- .setMessage(dialogText)
- .setPositiveButton(R.string.media_projection_action_text, this)
- .setNeutralButton(android.R.string.cancel, this);
- mDialog = dialogBuilder.create();
- }
+ final boolean overrideDisableSingleAppOption =
+ CompatChanges.isChangeEnabled(
+ OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+ mPackageName, getHostUserHandle());
+ MediaProjectionPermissionDialogDelegate delegate =
+ new MediaProjectionPermissionDialogDelegate(
+ dialogContext,
+ getMediaProjectionConfig(),
+ dialog -> {
+ ScreenShareOption selectedOption =
+ dialog.getSelectedScreenShareOption();
+ grantMediaProjectionPermission(selectedOption.getMode());
+ },
+ () -> finish(RECORD_CANCEL, /* projection= */ null),
+ hasCastingCapabilities,
+ appName,
+ overrideDisableSingleAppOption,
+ mUid,
+ mMediaProjectionMetricsLogger);
+ mDialog =
+ new AlertDialogWithDelegate(
+ dialogContext, R.style.Theme_SystemUI_Dialog, delegate);
if (savedInstanceState == null) {
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
mUid,
- appName == null
+ hasCastingCapabilities
? SessionCreationSource.CAST
: SessionCreationSource.APP);
}
@@ -366,7 +336,7 @@
setResult(RESULT_OK, intent);
finish(RECORD_CONTENT_DISPLAY, projection);
}
- if (isPartialScreenSharingEnabled() && screenShareMode == SINGLE_APP) {
+ if (screenShareMode == SINGLE_APP) {
IMediaProjection projection = MediaProjectionServiceHelper.createOrReuseProjection(
mUid, mPackageName, mReviewGrantedConsentRequired);
final Intent intent = new Intent(this,
@@ -437,8 +407,4 @@
return intent.getParcelableExtra(
MediaProjectionManager.EXTRA_MEDIA_PROJECTION_CONFIG);
}
-
- private boolean isPartialScreenSharingEnabled() {
- return mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
index 1f74716..fa8e13a 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt
@@ -25,9 +25,8 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeAlignment
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
/** Models UI state and handles user input for the Notifications Shade scene. */
@SysUISingleton
@@ -36,16 +35,15 @@
constructor(
shadeInteractor: ShadeInteractor,
) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- MutableStateFlow(
- mapOf(
- if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
- Swipe.Up
- } else {
- Swipe.Down
- } to SceneFamilies.Home,
- Back to SceneFamilies.Home,
- )
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ flowOf(
+ mapOf(
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ } to SceneFamilies.Home,
+ Back to SceneFamilies.Home,
)
- .asStateFlow()
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 9380d44..8d48c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -18,6 +18,7 @@
package com.android.systemui.power.domain.interactor
import android.os.PowerManager
+import com.android.systemui.camera.CameraGestureHelper
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorActual
import com.android.systemui.dagger.SysUISingleton
@@ -28,6 +29,7 @@
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -41,6 +43,7 @@
@FalsingCollectorActual private val falsingCollector: FalsingCollector,
private val screenOffAnimationController: ScreenOffAnimationController,
private val statusBarStateController: StatusBarStateController,
+ private val cameraGestureHelper: Provider<CameraGestureHelper>,
) {
/** Whether the screen is on or off. */
val isInteractive: Flow<Boolean> = repository.isInteractive
@@ -206,7 +209,13 @@
}
fun onCameraLaunchGestureDetected() {
- repository.updateWakefulness(powerButtonLaunchGestureTriggered = true)
+ if (
+ cameraGestureHelper
+ .get()
+ .canCameraGestureBeLaunched(statusBarStateController.getState())
+ ) {
+ repository.updateWakefulness(powerButtonLaunchGestureTriggered = true)
+ }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
index 5432793d..0f49c94 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
@@ -19,7 +19,7 @@
* all use cases. If you need more granular information about a waking/sleeping transition, use
* the [KeyguardTransitionInteractor].
*/
- internal val internalWakefulnessState: WakefulnessState = WakefulnessState.AWAKE,
+ val internalWakefulnessState: WakefulnessState = WakefulnessState.AWAKE,
val lastWakeReason: WakeSleepReason = WakeSleepReason.OTHER,
val lastSleepReason: WakeSleepReason = WakeSleepReason.OTHER,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 78f4b4b..072d322 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -30,14 +30,10 @@
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
import com.android.systemui.qs.panels.shared.model.PanelsLog
-import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
-import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModelImpl
import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModel
@@ -102,22 +98,6 @@
@Provides
@IntoSet
- fun provideStretchedGridLayout(
- gridLayout: StretchedGridLayout
- ): Pair<GridLayoutType, GridLayout> {
- return Pair(StretchedGridLayoutType, gridLayout)
- }
-
- @Provides
- @IntoSet
- fun providePartitionedGridLayout(
- gridLayout: PartitionedGridLayout
- ): Pair<GridLayoutType, GridLayout> {
- return Pair(PartitionedGridLayoutType, gridLayout)
- }
-
- @Provides
- @IntoSet
fun providePaginatedGridLayout(
gridLayout: PaginatedGridLayout
): Pair<GridLayoutType, GridLayout> {
@@ -148,22 +128,6 @@
@Provides
@IntoSet
- fun provideStretchedGridConsistencyInteractor(
- consistencyInteractor: NoopGridConsistencyInteractor
- ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
- return Pair(StretchedGridLayoutType, consistencyInteractor)
- }
-
- @Provides
- @IntoSet
- fun providePartitionedGridConsistencyInteractor(
- consistencyInteractor: NoopGridConsistencyInteractor
- ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
- return Pair(PartitionedGridLayoutType, consistencyInteractor)
- }
-
- @Provides
- @IntoSet
fun providePaginatedGridConsistencyInteractor(
@PaginatedBaseLayoutType consistencyInteractor: GridTypeConsistencyInteractor,
): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
index b1942fe..323f39b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
@@ -26,14 +26,5 @@
/** Grid type representing a scrollable vertical grid. */
data object InfiniteGridLayoutType : GridLayoutType
-/**
- * Grid type representing a scrollable vertical grid where tiles will stretch to fill in empty
- * spaces.
- */
-data object StretchedGridLayoutType : GridLayoutType
-
-/** Grid type grouping large tiles on top and icon tiles at the bottom. */
-data object PartitionedGridLayoutType : GridLayoutType
-
/** Grid type for a paginated list of tiles. It will delegate to some other layout type. */
data object PaginatedGridLayoutType : GridLayoutType
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
deleted file mode 100644
index 6c84edd..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
+++ /dev/null
@@ -1,404 +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.qs.panels.ui.compose
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.GridItemSpan
-import androidx.compose.foundation.lazy.grid.LazyGridScope
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Switch
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.PathEffect
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.addOutline
-import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.modifiers.background
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.PartitionedGridViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.res.R
-import javax.inject.Inject
-
-@SysUISingleton
-class PartitionedGridLayout @Inject constructor(private val viewModel: PartitionedGridViewModel) :
- PaginatableGridLayout {
- @Composable
- override fun TileGrid(
- tiles: List<TileViewModel>,
- modifier: Modifier,
- editModeStart: () -> Unit,
- ) {
- DisposableEffect(tiles) {
- val token = Any()
- tiles.forEach { it.startListening(token) }
- onDispose { tiles.forEach { it.stopListening(token) } }
- }
- val columns by viewModel.columns.collectAsStateWithLifecycle()
- val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
- val largeTileHeight = tileHeight()
- val iconTileHeight = tileHeight(showLabels)
- val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) }
-
- TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
- // Large tiles
- items(largeTiles.size, span = { GridItemSpan(2) }) { index ->
- Tile(
- tile = largeTiles[index],
- iconOnly = false,
- modifier = Modifier.height(largeTileHeight)
- )
- }
- fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
-
- // Small tiles
- items(smallTiles.size) { index ->
- Tile(
- tile = smallTiles[index],
- iconOnly = true,
- showLabels = showLabels,
- modifier = Modifier.height(iconTileHeight)
- )
- }
- }
- }
-
- @Composable
- override fun EditTileGrid(
- tiles: List<EditTileViewModel>,
- modifier: Modifier,
- onAddTile: (TileSpec, Int) -> Unit,
- onRemoveTile: (TileSpec) -> Unit,
- ) {
- val columns by viewModel.columns.collectAsStateWithLifecycle()
- val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
-
- 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)
- }
- val onDoubleTap: (TileSpec) -> Unit by rememberUpdatedState { tileSpec ->
- viewModel.resize(tileSpec, !viewModel.isIconTile(tileSpec))
- }
- val largeTileHeight = tileHeight()
- val iconTileHeight = tileHeight(showLabels)
- val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
-
- Column(
- verticalArrangement = Arrangement.spacedBy(tilePadding),
- modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState())
- ) {
- Row(
- modifier =
- Modifier.background(
- color = MaterialTheme.colorScheme.surfaceVariant,
- alpha = { 1f },
- shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))
- )
- .padding(tilePadding)
- ) {
- Column(Modifier.padding(start = tilePadding)) {
- Text(
- text = "Show text labels",
- color = MaterialTheme.colorScheme.onBackground,
- fontWeight = FontWeight.Bold
- )
- Text(
- text = "Display names under each tile",
- color = MaterialTheme.colorScheme.onBackground
- )
- }
- Spacer(modifier = Modifier.weight(1f))
- Switch(checked = showLabels, onCheckedChange = { viewModel.setShowLabels(it) })
- }
-
- CurrentTiles(
- tiles = currentTiles,
- largeTileHeight = largeTileHeight,
- iconTileHeight = iconTileHeight,
- tilePadding = tilePadding,
- onAdd = onAddTile,
- onRemove = onRemoveTile,
- onDoubleTap = onDoubleTap,
- isIconOnly = viewModel::isIconTile,
- columns = columns,
- showLabels = showLabels,
- dragAndDropState = dragAndDropState,
- )
- AvailableTiles(
- tiles = otherTiles.filter { !dragAndDropState.isMoving(it.tileSpec) },
- largeTileHeight = largeTileHeight,
- iconTileHeight = iconTileHeight,
- tilePadding = tilePadding,
- addTileToEnd = addTileToEnd,
- onRemove = onRemoveTile,
- onDoubleTap = onDoubleTap,
- isIconOnly = viewModel::isIconTile,
- showLabels = showLabels,
- columns = columns,
- dragAndDropState = dragAndDropState,
- )
- }
- }
-
- override fun splitIntoPages(
- tiles: List<TileViewModel>,
- rows: Int,
- columns: Int,
- ): List<List<TileViewModel>> {
- val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) }
-
- val sizedLargeTiles = largeTiles.map { SizedTile(it, 2) }
- val sizedSmallTiles = smallTiles.map { SizedTile(it, 1) }
- val largeTilesRows = PaginatableGridLayout.splitInRows(sizedLargeTiles, columns)
- val smallTilesRows = PaginatableGridLayout.splitInRows(sizedSmallTiles, columns)
- return (largeTilesRows + smallTilesRows).chunked(rows).map { it.flatten().map { it.tile } }
- }
-
- @Composable
- private fun CurrentTiles(
- tiles: List<EditTileViewModel>,
- largeTileHeight: Dp,
- iconTileHeight: Dp,
- tilePadding: Dp,
- onAdd: (TileSpec, Int) -> Unit,
- onRemove: (TileSpec) -> Unit,
- onDoubleTap: (TileSpec) -> Unit,
- isIconOnly: (TileSpec) -> Boolean,
- showLabels: Boolean,
- columns: Int,
- dragAndDropState: DragAndDropState,
- ) {
- val (smallTiles, largeTiles) = tiles.partition { isIconOnly(it.tileSpec) }
-
- val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding)
- val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding)
-
- CurrentTilesContainer {
- TileLazyGrid(
- columns = GridCells.Fixed(columns),
- modifier =
- Modifier.height(largeGridHeight)
- .dragAndDropTileList(dragAndDropState, { !isIconOnly(it) }, onAdd)
- ) {
- editTiles(
- tiles = largeTiles,
- clickAction = ClickAction.REMOVE,
- onClick = onRemove,
- onDoubleTap = onDoubleTap,
- isIconOnly = { false },
- dragAndDropState = dragAndDropState,
- acceptDrops = { !isIconOnly(it) },
- onDrop = onAdd,
- indicatePosition = true,
- )
- }
- }
-
- CurrentTilesContainer {
- TileLazyGrid(
- columns = GridCells.Fixed(columns),
- modifier =
- Modifier.height(smallGridHeight)
- .dragAndDropTileList(dragAndDropState, { isIconOnly(it) }, onAdd)
- ) {
- editTiles(
- tiles = smallTiles,
- clickAction = ClickAction.REMOVE,
- onClick = onRemove,
- onDoubleTap = onDoubleTap,
- isIconOnly = { true },
- showLabels = showLabels,
- dragAndDropState = dragAndDropState,
- acceptDrops = { isIconOnly(it) },
- onDrop = onAdd,
- indicatePosition = true,
- )
- }
- }
- }
-
- @Composable
- private fun AvailableTiles(
- tiles: List<EditTileViewModel>,
- largeTileHeight: Dp,
- iconTileHeight: Dp,
- tilePadding: Dp,
- addTileToEnd: (TileSpec) -> Unit,
- onRemove: (TileSpec) -> Unit,
- onDoubleTap: (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) }
-
- val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding)
- val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding)
- val largeGridHeightCustom =
- gridHeight(tilesCustom.size, iconTileHeight, columns, tilePadding)
-
- // Add up the height of all three grids and add padding in between
- 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)
- .dragAndDropTileList(dragAndDropState, { true }, onDrop)
- ) {
- // Large tiles
- editTiles(
- largeTiles,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- dragAndDropState,
- onDoubleTap = onDoubleTap,
- acceptDrops = { true },
- onDrop = onDrop,
- )
- fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
-
- // Small tiles
- editTiles(
- smallTiles,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- dragAndDropState,
- onDoubleTap = onDoubleTap,
- showLabels = showLabels,
- acceptDrops = { true },
- onDrop = onDrop,
- )
- fillUpRow(nTiles = smallTiles.size, columns = columns)
-
- // Custom tiles, all icons
- editTiles(
- tilesCustom,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- dragAndDropState,
- onDoubleTap = onDoubleTap,
- showLabels = showLabels,
- acceptDrops = { true },
- onDrop = onDrop,
- )
- }
- }
- }
-
- @Composable
- private fun CurrentTilesContainer(content: @Composable () -> Unit) {
- Box(
- Modifier.fillMaxWidth()
- .dashedBorder(
- color = MaterialTheme.colorScheme.onBackground.copy(alpha = .5f),
- shape = Dimensions.ContainerShape,
- )
- .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
- ) {
- content()
- }
- }
-
- @Composable
- private fun AvailableTilesContainer(content: @Composable () -> Unit) {
- Box(
- Modifier.fillMaxWidth()
- .background(
- color = MaterialTheme.colorScheme.background,
- alpha = { 1f },
- shape = Dimensions.ContainerShape,
- )
- .padding(dimensionResource(R.dimen.qs_tile_margin_vertical))
- ) {
- content()
- }
- }
-
- /** Fill up the rest of the row if it's not complete. */
- private fun LazyGridScope.fillUpRow(nTiles: Int, columns: Int) {
- if (nTiles % columns != 0) {
- item(span = { GridItemSpan(maxCurrentLineSpan) }) { Spacer(Modifier) }
- }
- }
-
- private fun Modifier.dashedBorder(
- color: Color,
- shape: Shape,
- ): Modifier {
- return this.drawWithContent {
- val outline = shape.createOutline(size, layoutDirection, this)
- val path = Path()
- path.addOutline(outline)
- val stroke =
- Stroke(
- width = 1.dp.toPx(),
- pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))
- )
- this.drawContent()
- drawPath(path = path, style = stroke, color = color)
- }
- }
-
- private object Dimensions {
- // Corner radius is half the height of a tile + padding
- val ContainerShape = RoundedCornerShape(48.dp)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
deleted file mode 100644
index 3e48245..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
+++ /dev/null
@@ -1,139 +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.qs.panels.ui.compose
-
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.GridItemSpan
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.dimensionResource
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.TileRow
-import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.res.R
-import javax.inject.Inject
-
-@SysUISingleton
-class StretchedGridLayout
-@Inject
-constructor(
- private val iconTilesViewModel: IconTilesViewModel,
- private val gridSizeViewModel: FixedColumnsSizeViewModel,
-) : GridLayout {
-
- @Composable
- override fun TileGrid(
- tiles: List<TileViewModel>,
- modifier: Modifier,
- editModeStart: () -> Unit,
- ) {
- DisposableEffect(tiles) {
- val token = Any()
- tiles.forEach { it.startListening(token) }
- onDispose { tiles.forEach { it.stopListening(token) } }
- }
-
- // Tile widths [normal|stretched]
- // Icon [3 | 4]
- // Large [6 | 8]
- val columns = 12
- val stretchedTiles =
- remember(tiles) {
- val sizedTiles =
- tiles.map {
- SizedTile(
- it,
- if (iconTilesViewModel.isIconTile(it.spec)) {
- 3
- } else {
- 6
- }
- )
- }
- splitInRows(sizedTiles, columns)
- }
-
- TileLazyGrid(columns = GridCells.Fixed(columns), modifier = modifier) {
- items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index ->
- Tile(
- tile = stretchedTiles[index].tile,
- iconOnly = iconTilesViewModel.isIconTile(stretchedTiles[index].tile.spec),
- modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
- )
- }
- }
- }
-
- @Composable
- override fun EditTileGrid(
- tiles: List<EditTileViewModel>,
- modifier: Modifier,
- onAddTile: (TileSpec, Int) -> Unit,
- onRemoveTile: (TileSpec) -> Unit
- ) {
- val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
-
- DefaultEditTileGrid(
- tiles = tiles,
- isIconOnly = iconTilesViewModel::isIconTile,
- columns = columns,
- modifier = modifier,
- onAddTile = onAddTile,
- onRemoveTile = onRemoveTile,
- onResize = iconTilesViewModel::resize,
- )
- }
-
- private fun splitInRows(
- tiles: List<SizedTile<TileViewModel>>,
- columns: Int
- ): List<SizedTile<TileViewModel>> {
- val row = TileRow<TileViewModel>(columns)
-
- return buildList {
- for (tile in tiles) {
- if (row.maybeAddTile(tile)) {
- if (row.isFull()) {
- // Row is full, no need to stretch tiles
- addAll(row.tiles)
- row.clear()
- }
- } else {
- if (row.isFull()) {
- addAll(row.tiles)
- } else {
- // Stretching tiles when row isn't full
- addAll(row.tiles.map { it.copy(width = it.width + (it.width / 3)) })
- }
- row.clear()
- row.maybeAddTile(tile)
- }
- }
- addAll(row.tiles)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index dbfe818..abc0453 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -586,6 +586,15 @@
)
)
)
+ } else {
+ if (isLongClickable) {
+ info.addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+ resources.getString(R.string.accessibility_long_click_tile)
+ )
+ )
+ }
}
if (!TextUtils.isEmpty(accessibilityClass)) {
info.className =
@@ -597,14 +606,6 @@
if (Switch::class.java.name == accessibilityClass) {
info.isChecked = tileState
info.isCheckable = true
- if (isLongClickable) {
- info.addAction(
- AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
- resources.getString(R.string.accessibility_long_click_tile)
- )
- )
- }
}
}
if (position != INVALID) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
index 53594bb..f702da4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
@@ -27,7 +27,6 @@
subtitleIdsMap["cell"] = R.array.tile_states_cell
subtitleIdsMap["battery"] = R.array.tile_states_battery
subtitleIdsMap["dnd"] = R.array.tile_states_dnd
- subtitleIdsMap["modes"] = R.array.tile_states_modes
subtitleIdsMap["flashlight"] = R.array.tile_states_flashlight
subtitleIdsMap["rotation"] = R.array.tile_states_rotation
subtitleIdsMap["bt"] = R.array.tile_states_bt
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index bdf935e..b927134 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -49,6 +49,7 @@
import com.android.systemui.animation.Expandable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.RefactorFlagUtils;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -105,6 +106,11 @@
) {
super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
+
+ // If the flag is on, this shouldn't run at all since the modes tile replaces the DND tile.
+ RefactorFlagUtils.INSTANCE.assertInLegacyMode(android.app.Flags.modesUi(),
+ android.app.Flags.FLAG_MODES_UI);
+
mController = zenModeController;
mSharedPreferences = sharedPreferences;
mController.observe(getLifecycle(), mZenCallback);
@@ -253,18 +259,20 @@
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
state.contentDescription =
mContext.getString(R.string.accessibility_quick_settings_dnd) + ", "
- + state.secondaryLabel;
+ + state.secondaryLabel;
break;
case Global.ZEN_MODE_NO_INTERRUPTIONS:
state.contentDescription =
mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
- mContext.getString(R.string.accessibility_quick_settings_dnd_none_on)
+ mContext.getString(
+ R.string.accessibility_quick_settings_dnd_none_on)
+ ", " + state.secondaryLabel;
break;
case ZEN_MODE_ALARMS:
state.contentDescription =
mContext.getString(R.string.accessibility_quick_settings_dnd) + ", " +
- mContext.getString(R.string.accessibility_quick_settings_dnd_alarms_on)
+ mContext.getString(
+ R.string.accessibility_quick_settings_dnd_alarms_on)
+ ", " + state.secondaryLabel;
break;
default:
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index a300031..2a33a16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -23,13 +23,15 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.internal.R.attr.contentDescription
import com.android.internal.logging.MetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.RefactorFlagUtils.isUnexpectedlyInLegacyMode
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.plugins.qs.QSTile.BooleanState
+import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
@@ -63,7 +65,7 @@
private val tileMapper: ModesTileMapper,
private val userActionInteractor: ModesTileUserActionInteractor,
) :
- QSTileImpl<BooleanState>(
+ QSTileImpl<QSTile.State>(
host,
uiEventLogger,
backgroundLooper,
@@ -79,6 +81,8 @@
private val config = qsTileConfigProvider.getConfig(TILE_SPEC)
init {
+ /* Check if */ isUnexpectedlyInLegacyMode(Flags.modesUi(), Flags.FLAG_MODES_UI)
+
lifecycle.coroutineScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
dataInteractor.tileData().collect { refreshState(it) }
@@ -90,7 +94,7 @@
override fun getTileLabel(): CharSequence = tileState.label
- override fun newTileState() = BooleanState()
+ override fun newTileState() = QSTile.State()
override fun handleClick(expandable: Expandable?) = runBlocking {
userActionInteractor.handleClick(expandable)
@@ -98,22 +102,22 @@
override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
- override fun handleUpdateState(booleanState: BooleanState?, arg: Any?) {
+ override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
if (arg is ModesTileModel) {
tileState = tileMapper.map(config, arg)
- booleanState?.apply {
- state = tileState.activationState.legacyState
+ state?.apply {
+ this.state = tileState.activationState.legacyState
icon = ResourceIcon.get(tileState.iconRes ?: R.drawable.qs_dnd_icon_off)
label = tileLabel
secondaryLabel = tileState.secondaryLabel
contentDescription = tileState.contentDescription
- forceExpandIcon = true
+ expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
}
}
}
companion object {
- const val TILE_SPEC = "modes"
+ const val TILE_SPEC = "dnd"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 158eb6e..b2873c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -736,7 +736,8 @@
// Set network description for the carrier network when connecting to the carrier network
// under the airplane mode ON.
if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
- summary = context.getString(R.string.preference_summary_default_combination,
+ summary = context.getString(
+ com.android.settingslib.R.string.preference_summary_default_combination,
context.getString(
isForDds // if nonDds is active, explains Dds status as poor connection
? (isOnNonDds ? R.string.mobile_data_poor_connection
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index 31e91aa8..92efa40 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -19,20 +19,27 @@
import android.app.Flags
import android.os.UserHandle
import com.android.settingslib.notification.data.repository.ZenModeRepository
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-class ModesTileDataInteractor @Inject constructor(val zenModeRepository: ZenModeRepository) :
- QSTileDataInteractor<ModesTileModel> {
- private val zenModeActive =
+class ModesTileDataInteractor
+@Inject
+constructor(
+ val zenModeRepository: ZenModeRepository,
+ @Background val bgDispatcher: CoroutineDispatcher,
+) : QSTileDataInteractor<ModesTileModel> {
+ private val activeModes =
zenModeRepository.modes
- .map { modes -> modes.any { mode -> mode.isActive } }
+ .map { modes -> modes.filter { mode -> mode.isActive }.map { it.name } }
.distinctUntilChanged()
override fun tileData(
@@ -45,7 +52,10 @@
*
* TODO(b/299909989): Remove after the transition.
*/
- fun tileData() = zenModeActive.map { ModesTileModel(isActivated = it) }
+ fun tileData() =
+ activeModes
+ .map { ModesTileModel(isActivated = it.isNotEmpty(), activeModes = it) }
+ .flowOn(bgDispatcher)
override fun availability(user: UserHandle): Flow<Boolean> = flowOf(Flags.modesUi())
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
index e44413a..cc509ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
@@ -15,4 +15,4 @@
*/
package com.android.systemui.qs.tiles.impl.modes.domain.model
-data class ModesTileModel(val isActivated: Boolean)
+data class ModesTileModel(val isActivated: Boolean, val activeModes: List<String>)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 7048ada..7afdb75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -17,6 +17,8 @@
package com.android.systemui.qs.tiles.impl.modes.ui
import android.content.res.Resources
+import android.icu.text.MessageFormat
+import android.widget.Button
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
@@ -24,6 +26,7 @@
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import java.util.Locale
import javax.inject.Inject
class ModesTileMapper
@@ -46,19 +49,32 @@
contentDescription = null,
)
this.icon = { icon }
- if (data.isActivated) {
- activationState = QSTileState.ActivationState.ACTIVE
- secondaryLabel = "Some modes enabled idk" // TODO(b/346519570)
- } else {
- activationState = QSTileState.ActivationState.INACTIVE
- secondaryLabel = "Off" // TODO(b/346519570)
- }
- contentDescription = label
+ activationState =
+ if (data.isActivated) {
+ QSTileState.ActivationState.ACTIVE
+ } else {
+ QSTileState.ActivationState.INACTIVE
+ }
+ secondaryLabel = getModesStatus(data, resources)
+ contentDescription = "$label. $secondaryLabel"
supportedActions =
setOf(
QSTileState.UserAction.CLICK,
QSTileState.UserAction.LONG_CLICK,
)
sideViewIcon = QSTileState.SideViewIcon.Chevron
+ expandedAccessibilityClass = Button::class
}
+
+ private fun getModesStatus(data: ModesTileModel, resources: Resources): String {
+ val msgFormat =
+ MessageFormat(resources.getString(R.string.zen_mode_active_modes), Locale.getDefault())
+ val count = data.activeModes.count()
+ val args: MutableMap<String, Any> = HashMap()
+ args["count"] = count
+ if (count >= 1) {
+ args["mode"] = data.activeModes[0]
+ }
+ return msgFormat.format(args)
+ }
}
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 ba0a8d6..2cdcc24 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
@@ -19,7 +19,6 @@
import android.content.Context
import android.os.UserHandle
import android.util.Log
-import androidx.annotation.GuardedBy
import com.android.internal.logging.InstanceId
import com.android.systemui.Dumpable
import com.android.systemui.animation.Expandable
@@ -34,6 +33,7 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.io.PrintWriter
+import java.util.concurrent.CopyOnWriteArraySet
import java.util.function.Supplier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -57,10 +57,8 @@
private val context
get() = qsHost.context
- @GuardedBy("callbacks")
- private val callbacks: MutableCollection<QSTile.Callback> = mutableSetOf()
- @GuardedBy("listeningClients")
- private val listeningClients: MutableCollection<Any> = mutableSetOf()
+ private val callbacks = CopyOnWriteArraySet<QSTile.Callback>()
+ private val listeningClients = CopyOnWriteArraySet<Any>()
// Cancels the jobs when the adapter is no longer alive
private var tileAdapterJob: Job? = null
@@ -113,19 +111,17 @@
override fun addCallback(callback: QSTile.Callback?) {
callback ?: return
- synchronized(callbacks) {
- callbacks.add(callback)
- state?.let(callback::onStateChanged)
- }
+ callbacks.add(callback)
+ state?.let(callback::onStateChanged)
}
override fun removeCallback(callback: QSTile.Callback?) {
callback ?: return
- synchronized(callbacks) { callbacks.remove(callback) }
+ callbacks.remove(callback)
}
override fun removeCallbacks() {
- synchronized(callbacks) { callbacks.clear() }
+ callbacks.clear()
}
override fun click(expandable: Expandable?) {
@@ -163,32 +159,28 @@
override fun setListening(client: Any?, listening: Boolean) {
client ?: return
- synchronized(listeningClients) {
- if (listening) {
- listeningClients.add(client)
- if (listeningClients.size == 1) {
- stateJob =
- qsTileViewModel.state
- .filterNotNull()
- .map { mapState(context, it, qsTileViewModel.config) }
- .onEach { legacyState ->
- synchronized(callbacks) {
- callbacks.forEach { it.onStateChanged(legacyState) }
- }
- }
- .launchIn(applicationScope)
- }
- } else {
- listeningClients.remove(client)
- if (listeningClients.isEmpty()) {
- stateJob?.cancel()
- }
+ if (listening) {
+ listeningClients.add(client)
+ if (listeningClients.size == 1) {
+ stateJob =
+ qsTileViewModel.state
+ .filterNotNull()
+ .map { mapState(context, it, qsTileViewModel.config) }
+ .onEach { legacyState ->
+ callbacks.forEach { it.onStateChanged(legacyState) }
+ }
+ .launchIn(applicationScope)
+ }
+ } else {
+ listeningClients.remove(client)
+ if (listeningClients.isEmpty()) {
+ stateJob?.cancel()
}
}
}
override fun isListening(): Boolean =
- synchronized(listeningClients) { listeningClients.isNotEmpty() }
+ listeningClients.isNotEmpty()
override fun setDetailListening(show: Boolean) {
// do nothing like QSTileImpl
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 79cdfec..b1cc55d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -25,7 +25,6 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -38,20 +37,17 @@
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the quick settings scene. */
@SysUISingleton
class QuickSettingsSceneViewModel
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
val brightnessMirrorViewModel: BrightnessMirrorViewModel,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val qsSceneAdapter: QSSceneAdapter,
@@ -61,55 +57,35 @@
sceneBackInteractor: SceneBackInteractor,
val mediaCarouselInteractor: MediaCarouselInteractor,
) {
- private val backScene: StateFlow<SceneKey> =
+ private val backScene: Flow<SceneKey> =
sceneBackInteractor.backScene
.filter { it != Scenes.QuickSettings }
.map { it ?: Scenes.Shade }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = Scenes.Shade,
- )
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
combine(
- qsSceneAdapter.isCustomizerShowing,
- backScene,
- transform = ::destinationScenes,
- )
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
- backScene = backScene.value,
- ),
- )
+ qsSceneAdapter.isCustomizerShowing,
+ backScene,
+ ) { isCustomizing, backScene ->
+ buildMap<UserAction, UserActionResult> {
+ if (isCustomizing) {
+ // TODO(b/332749288) Empty map so there are no back handlers and back can close
+ // customizer
- val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
-
- private fun destinationScenes(
- isCustomizing: Boolean,
- backScene: SceneKey?,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- if (isCustomizing) {
- // TODO(b/332749288) Empty map so there are no back handlers and back can close
- // customizer
-
- // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
- // while customizing
- } else {
- put(Back, UserActionResult(backScene ?: Scenes.Shade))
- put(Swipe(SwipeDirection.Up), UserActionResult(backScene ?: Scenes.Shade))
- put(
- Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up),
- UserActionResult(SceneFamilies.Home),
- )
+ // TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
+ // while customizing
+ } else {
+ put(Back, UserActionResult(backScene))
+ put(Swipe(SwipeDirection.Up), UserActionResult(backScene))
+ put(
+ Swipe(fromSource = Edge.Bottom, direction = SwipeDirection.Up),
+ UserActionResult(SceneFamilies.Home),
+ )
+ }
}
}
- }
+
+ val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
private val footerActionsControllerInitialized = AtomicBoolean(false)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
index 66fcbacb..e012f2c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt
@@ -21,17 +21,13 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeAlignment
import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
/** Models UI state and handles user input for the Quick Settings Shade scene. */
@SysUISingleton
@@ -41,33 +37,22 @@
private val shadeInteractor: ShadeInteractor,
val overlayShadeViewModel: OverlayShadeViewModel,
val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
- @Application applicationScope: CoroutineScope,
) {
- val isEditing: StateFlow<Boolean> = quickSettingsContainerViewModel.editModeViewModel.isEditing
-
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- isEditing
- .map { editing -> destinations(editing) }
- .stateIn(
- applicationScope,
- SharingStarted.WhileSubscribed(),
- destinations(isEditing.value)
- )
-
- private fun destinations(editing: Boolean): Map<UserAction, UserActionResult> {
- return buildMap {
- put(
- if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
- Swipe.Up
- } else {
- Swipe.Down
- },
- UserActionResult(SceneFamilies.Home)
- )
- if (!editing) {
- put(Back, UserActionResult(SceneFamilies.Home))
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ quickSettingsContainerViewModel.editModeViewModel.isEditing.map { editing ->
+ buildMap {
+ put(
+ if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
+ Swipe.Up
+ } else {
+ Swipe.Down
+ },
+ UserActionResult(SceneFamilies.Home)
+ )
+ if (!editing) {
+ put(Back, UserActionResult(SceneFamilies.Home))
+ }
}
}
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index dd2dbf3..f8b3ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -35,7 +35,6 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
@@ -154,10 +153,7 @@
SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
)
- if (
- flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) &&
- !state.hasUserApprovedScreenRecording
- ) {
+ if (!state.hasUserApprovedScreenRecording) {
mainExecutor.execute {
ScreenCapturePermissionDialogDelegate(factory, state).createDialog().apply {
setOnCancelListener { screenRecordSwitch.isChecked = false }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 939d5bc..c7190c3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -19,7 +19,8 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import kotlinx.coroutines.flow.StateFlow
+import com.android.systemui.activatable.Activatable
+import kotlinx.coroutines.flow.Flow
/**
* Defines interface for classes that can describe a "scene".
@@ -29,11 +30,13 @@
* based on either user action (for example, swiping down while on the lock screen scene may switch
* to the shade scene).
*/
-interface Scene {
+interface Scene : Activatable {
/** Uniquely-identifying key for this scene. The key must be unique within its container. */
val key: SceneKey
+ override suspend fun activate() = Unit
+
/**
* The mapping between [UserAction] and destination [UserActionResult]s.
*
@@ -54,5 +57,5 @@
* type is not currently active in the scene and should be ignored by the framework, while the
* current scene is this one.
*/
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>>
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>>
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index c20d577..d31d6f4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -103,6 +103,7 @@
windowInsets = windowInsets,
sceneByKey = sortedSceneByKey,
dataSourceDelegator = dataSourceDelegator,
+ containerConfig = containerConfig,
)
.also { it.id = R.id.scene_container_root_composable }
)
@@ -141,6 +142,7 @@
windowInsets: StateFlow<WindowInsets?>,
sceneByKey: Map<SceneKey, Scene>,
dataSourceDelegator: SceneDataSourceDelegator,
+ containerConfig: SceneContainerConfig,
): View {
return ComposeView(context).apply {
setContent {
@@ -153,6 +155,7 @@
viewModel = viewModel,
sceneByKey =
sceneByKey.mapValues { (_, scene) -> scene as ComposableScene },
+ initialSceneKey = containerConfig.initialSceneKey,
dataSourceDelegator = dataSourceDelegator,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
index 9f48ee9..b739ffe 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -23,79 +23,61 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.TransitionKeys.OpenBottomShade
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
@SysUISingleton
class GoneSceneViewModel
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
private val shadeInteractor: ShadeInteractor,
) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- shadeInteractor.shadeMode
- .map(::destinationScenes)
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- shadeMode = shadeInteractor.shadeMode.value,
- )
- )
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
+ shadeInteractor.shadeMode.map { shadeMode ->
+ buildMap {
+ if (
+ shadeMode is ShadeMode.Single ||
+ // TODO(b/338577208): Remove this once we add Dual Shade invocation zones.
+ shadeMode is ShadeMode.Dual
+ ) {
+ if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
+ put(
+ Swipe(
+ pointerCount = 2,
+ fromSource = Edge.Bottom,
+ direction = SwipeDirection.Up,
+ ),
+ UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade)
+ )
+ } else {
+ put(
+ Swipe(
+ pointerCount = 2,
+ fromSource = Edge.Top,
+ direction = SwipeDirection.Down,
+ ),
+ UserActionResult(SceneFamilies.QuickSettings)
+ )
+ }
+ }
- private fun destinationScenes(
- shadeMode: ShadeMode,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- if (
- shadeMode is ShadeMode.Single ||
- // TODO(b/338577208): Remove this once we add Dual Shade invocation zones.
- shadeMode is ShadeMode.Dual
- ) {
if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
- put(
- Swipe(
- pointerCount = 2,
- fromSource = Edge.Bottom,
- direction = SwipeDirection.Up,
- ),
- UserActionResult(SceneFamilies.QuickSettings, OpenBottomShade)
- )
+ put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade))
} else {
put(
- Swipe(
- pointerCount = 2,
- fromSource = Edge.Top,
- direction = SwipeDirection.Down,
- ),
- UserActionResult(SceneFamilies.QuickSettings)
+ Swipe.Down,
+ UserActionResult(
+ SceneFamilies.NotifShade,
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ )
)
}
}
-
- if (shadeInteractor.shadeAlignment == Alignment.BottomEnd) {
- put(Swipe.Up, UserActionResult(SceneFamilies.NotifShade, OpenBottomShade))
- } else {
- put(
- Swipe.Down,
- UserActionResult(
- SceneFamilies.NotifShade,
- ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- )
- )
- }
}
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 46c5861..a8a78a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -171,11 +171,8 @@
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
- return (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
- ? mScreenRecordPermissionDialogDelegateFactory
- .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
- : mScreenRecordDialogFactory
- .create(this, onStartRecordingClicked))
+ return mScreenRecordPermissionDialogDelegateFactory
+ .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
.createDialog();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/src/com/android/systemui/screenshot/InteractiveScreenshotHandler.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/screenshot/InteractiveScreenshotHandler.kt
index 37c9552..26405f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/InteractiveScreenshotHandler.kt
@@ -14,11 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.screenshot
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
+import android.view.Display
-val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
+interface InteractiveScreenshotHandler : ScreenshotHandler {
+ fun isPendingSharedTransition(): Boolean
+
+ fun requestDismissal(event: ScreenshotEvent)
+
+ fun removeWindow()
+
+ fun onDestroy()
+
+ interface Factory {
+ fun create(display: Display): InteractiveScreenshotHandler
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
new file mode 100644
index 0000000..a2583e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java
@@ -0,0 +1,848 @@
+/*
+ * 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.screenshot;
+
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+
+import static com.android.systemui.Flags.screenshotPrivateProfileAccessibilityAnnouncementFix;
+import static com.android.systemui.Flags.screenshotSaveImageExporter;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_INPUT;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_UI;
+import static com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW;
+import static com.android.systemui.screenshot.LogConfig.logTag;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
+import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ICompatCameraControlCallback;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.ScrollCaptureResponse;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.Toast;
+import android.window.WindowContext;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.policy.PhoneWindow;
+import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.res.R;
+import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
+import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor;
+import com.android.systemui.util.Assert;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+import kotlin.Unit;
+
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+import javax.inject.Provider;
+
+/**
+ * Controls the state and flow for screenshots.
+ */
+public class LegacyScreenshotController implements InteractiveScreenshotHandler {
+ private static final String TAG = logTag(LegacyScreenshotController.class);
+
+ // From WizardManagerHelper.java
+ private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
+
+ static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
+
+ private final WindowContext mContext;
+ private final FeatureFlags mFlags;
+ private final ScreenshotShelfViewProxy mViewProxy;
+ private final ScreenshotNotificationsController mNotificationsController;
+ private final ScreenshotSmartActions mScreenshotSmartActions;
+ private final UiEventLogger mUiEventLogger;
+ private final ImageExporter mImageExporter;
+ private final ImageCapture mImageCapture;
+ private final Executor mMainExecutor;
+ private final ExecutorService mBgExecutor;
+ private final BroadcastSender mBroadcastSender;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ScreenshotActionsController mActionsController;
+
+ private final WindowManager mWindowManager;
+ private final WindowManager.LayoutParams mWindowLayoutParams;
+ @Nullable
+ private final ScreenshotSoundController mScreenshotSoundController;
+ private final PhoneWindow mWindow;
+ private final Display mDisplay;
+ private final ScrollCaptureExecutor mScrollCaptureExecutor;
+ private final ScreenshotNotificationSmartActionsProvider
+ mScreenshotNotificationSmartActionsProvider;
+ private final TimeoutHandler mScreenshotHandler;
+ private final UserManager mUserManager;
+ private final AssistContentRequester mAssistContentRequester;
+ private final ActionExecutor mActionExecutor;
+
+
+ private final MessageContainerController mMessageContainerController;
+ private final AnnouncementResolver mAnnouncementResolver;
+ private Bitmap mScreenBitmap;
+ private SaveImageInBackgroundTask mSaveInBgTask;
+ private boolean mScreenshotTakenInPortrait;
+ private boolean mAttachRequested;
+ private boolean mDetachRequested;
+ private Animator mScreenshotAnimation;
+ private RequestCallback mCurrentRequestCallback;
+ private String mPackageName = "";
+ private final BroadcastReceiver mCopyBroadcastReceiver;
+
+ /** Tracks config changes that require re-creating UI */
+ private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
+ ActivityInfo.CONFIG_ORIENTATION
+ | ActivityInfo.CONFIG_LAYOUT_DIRECTION
+ | ActivityInfo.CONFIG_LOCALE
+ | ActivityInfo.CONFIG_UI_MODE
+ | ActivityInfo.CONFIG_SCREEN_LAYOUT
+ | ActivityInfo.CONFIG_ASSETS_PATHS);
+
+
+ @AssistedInject
+ LegacyScreenshotController(
+ Context context,
+ WindowManager windowManager,
+ FeatureFlags flags,
+ ScreenshotShelfViewProxy.Factory viewProxyFactory,
+ ScreenshotSmartActions screenshotSmartActions,
+ ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
+ UiEventLogger uiEventLogger,
+ ImageExporter imageExporter,
+ ImageCapture imageCapture,
+ @Main Executor mainExecutor,
+ ScrollCaptureExecutor scrollCaptureExecutor,
+ TimeoutHandler timeoutHandler,
+ BroadcastSender broadcastSender,
+ BroadcastDispatcher broadcastDispatcher,
+ ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
+ ScreenshotActionsController.Factory screenshotActionsControllerFactory,
+ ActionExecutor.Factory actionExecutorFactory,
+ UserManager userManager,
+ AssistContentRequester assistContentRequester,
+ MessageContainerController messageContainerController,
+ Provider<ScreenshotSoundController> screenshotSoundController,
+ AnnouncementResolver announcementResolver,
+ @Assisted Display display
+ ) {
+ mScreenshotSmartActions = screenshotSmartActions;
+ mNotificationsController = screenshotNotificationsControllerFactory.create(
+ display.getDisplayId());
+ mUiEventLogger = uiEventLogger;
+ mImageExporter = imageExporter;
+ mImageCapture = imageCapture;
+ mMainExecutor = mainExecutor;
+ mScrollCaptureExecutor = scrollCaptureExecutor;
+ mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider;
+ mBgExecutor = Executors.newSingleThreadExecutor();
+ mBroadcastSender = broadcastSender;
+ mBroadcastDispatcher = broadcastDispatcher;
+
+ mScreenshotHandler = timeoutHandler;
+ mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
+
+ mDisplay = display;
+ mWindowManager = windowManager;
+ final Context displayContext = context.createDisplayContext(display);
+ mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
+ mFlags = flags;
+ mUserManager = userManager;
+ mMessageContainerController = messageContainerController;
+ mAssistContentRequester = assistContentRequester;
+ mAnnouncementResolver = announcementResolver;
+
+ mViewProxy = viewProxyFactory.getProxy(mContext, mDisplay.getDisplayId());
+
+ mScreenshotHandler.setOnTimeoutRunnable(() -> {
+ if (DEBUG_UI) {
+ Log.d(TAG, "Corner timeout hit");
+ }
+ mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
+ });
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
+ mWindowLayoutParams.setTitle("ScreenshotAnimation");
+
+ mWindow = FloatingWindowUtil.getFloatingWindow(mContext);
+ mWindow.setWindowManager(mWindowManager, null, null);
+
+ mConfigChanges.applyNewConfig(context.getResources());
+ reloadAssets();
+
+ mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy,
+ () -> {
+ finishDismiss();
+ return Unit.INSTANCE;
+ });
+ mActionsController = screenshotActionsControllerFactory.getController(mActionExecutor);
+
+
+ // Sound is only reproduced from the controller of the default display.
+ if (mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ mScreenshotSoundController = screenshotSoundController.get();
+ } else {
+ mScreenshotSoundController = null;
+ }
+
+ mCopyBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ClipboardOverlayController.COPY_OVERLAY_ACTION.equals(intent.getAction())) {
+ mViewProxy.requestDismissal(SCREENSHOT_DISMISSED_OTHER);
+ }
+ }
+ };
+ mBroadcastDispatcher.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
+ ClipboardOverlayController.COPY_OVERLAY_ACTION), null, null,
+ Context.RECEIVER_NOT_EXPORTED, ClipboardOverlayController.SELF_PERMISSION);
+ }
+
+ @Override
+ public void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher,
+ RequestCallback requestCallback) {
+ Assert.isMainThread();
+
+ mCurrentRequestCallback = requestCallback;
+ if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+ && screenshot.getBitmap() == null) {
+ Rect bounds = getFullScreenRect();
+ screenshot.setBitmap(mImageCapture.captureDisplay(mDisplay.getDisplayId(), bounds));
+ screenshot.setScreenBounds(bounds);
+ }
+
+ if (screenshot.getBitmap() == null) {
+ Log.e(TAG, "handleScreenshot: Screenshot bitmap was null");
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ if (mCurrentRequestCallback != null) {
+ mCurrentRequestCallback.reportError();
+ }
+ return;
+ }
+
+ mScreenBitmap = screenshot.getBitmap();
+ String oldPackageName = mPackageName;
+ mPackageName = screenshot.getPackageNameString();
+
+ if (!isUserSetupComplete(Process.myUserHandle())) {
+ Log.w(TAG, "User setup not complete, displaying toast only");
+ // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
+ // and sharing shouldn't be exposed to the user.
+ saveScreenshotAndToast(screenshot, finisher);
+ return;
+ }
+
+ mBroadcastSender.sendBroadcast(new Intent(ClipboardOverlayController.SCREENSHOT_ACTION),
+ ClipboardOverlayController.SELF_PERMISSION);
+
+ mScreenshotTakenInPortrait =
+ mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
+
+ // Optimizations
+ mScreenBitmap.setHasAlpha(false);
+ mScreenBitmap.prepareToDraw();
+
+ prepareViewForNewScreenshot(screenshot, oldPackageName);
+
+ final UUID requestId;
+ requestId = mActionsController.setCurrentScreenshot(screenshot);
+ saveScreenshotInBackground(screenshot, requestId, finisher, result -> {
+ if (result.uri != null) {
+ ScreenshotSavedResult savedScreenshot = new ScreenshotSavedResult(
+ result.uri, screenshot.getUserOrDefault(), result.timestamp);
+ mActionsController.setCompletedScreenshot(requestId, savedScreenshot);
+ }
+ });
+
+ if (screenshot.getTaskId() >= 0) {
+ mAssistContentRequester.requestAssistContent(
+ screenshot.getTaskId(),
+ assistContent ->
+ mActionsController.onAssistContent(requestId, assistContent));
+ } else {
+ mActionsController.onAssistContent(requestId, null);
+ }
+
+ // The window is focusable by default
+ setWindowFocusable(true);
+ mViewProxy.requestFocus();
+
+ enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle());
+
+ attachWindow();
+
+ boolean showFlash;
+ if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ if (screenshot.getScreenBounds() != null
+ && aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
+ screenshot.getScreenBounds())) {
+ showFlash = false;
+ } else {
+ showFlash = true;
+ screenshot.setInsets(Insets.NONE);
+ screenshot.setScreenBounds(new Rect(0, 0, screenshot.getBitmap().getWidth(),
+ screenshot.getBitmap().getHeight()));
+ }
+ } else {
+ showFlash = true;
+ }
+
+ mViewProxy.prepareEntranceAnimation(
+ () -> startAnimation(screenshot.getScreenBounds(), showFlash,
+ () -> mMessageContainerController.onScreenshotTaken(screenshot)));
+
+ mViewProxy.setScreenshot(screenshot);
+
+ // ignore system bar insets for the purpose of window layout
+ mWindow.getDecorView().setOnApplyWindowInsetsListener(
+ (v, insets) -> WindowInsets.CONSUMED);
+ }
+
+ void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) {
+ withWindowAttached(() -> {
+ if (screenshotPrivateProfileAccessibilityAnnouncementFix()) {
+ mAnnouncementResolver.getScreenshotAnnouncement(
+ screenshot.getUserHandle().getIdentifier(),
+ mViewProxy::announceForAccessibility);
+ } else {
+ if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
+ mViewProxy.announceForAccessibility(mContext.getResources().getString(
+ R.string.screenshot_saving_work_profile_title));
+ } else {
+ mViewProxy.announceForAccessibility(
+ mContext.getResources().getString(R.string.screenshot_saving_title));
+ }
+ }
+ });
+
+ mViewProxy.reset();
+
+ if (mViewProxy.isAttachedToWindow()) {
+ // if we didn't already dismiss for another reason
+ if (!mViewProxy.isDismissing()) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0,
+ oldPackageName);
+ }
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
+ + "(dismissing=" + mViewProxy.isDismissing() + ")");
+ }
+ }
+
+ mViewProxy.setPackageName(mPackageName);
+ }
+
+ /**
+ * Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already
+ * being dismissed)
+ */
+ @Override
+ public void requestDismissal(ScreenshotEvent event) {
+ mViewProxy.requestDismissal(event);
+ }
+
+ @Override
+ public boolean isPendingSharedTransition() {
+ return mActionExecutor.isPendingSharedTransition();
+ }
+
+ // Any cleanup needed when the service is being destroyed.
+ @Override
+ public void onDestroy() {
+ if (mSaveInBgTask != null) {
+ // just log success/failure for the pre-existing screenshot
+ mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
+ }
+ removeWindow();
+ releaseMediaPlayer();
+ releaseContext();
+ mBgExecutor.shutdown();
+ }
+
+ /**
+ * Release the constructed window context.
+ */
+ private void releaseContext() {
+ mBroadcastDispatcher.unregisterReceiver(mCopyBroadcastReceiver);
+ mContext.release();
+ }
+
+ private void releaseMediaPlayer() {
+ if (mScreenshotSoundController == null) return;
+ mScreenshotSoundController.releaseScreenshotSoundAsync();
+ }
+
+ /**
+ * Update resources on configuration change. Reinflate for theme/color changes.
+ */
+ private void reloadAssets() {
+ if (DEBUG_UI) {
+ Log.d(TAG, "reloadAssets()");
+ }
+
+ mMessageContainerController.setView(mViewProxy.getView());
+ mViewProxy.setCallbacks(new ScreenshotShelfViewProxy.ScreenshotViewCallback() {
+ @Override
+ public void onUserInteraction() {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "onUserInteraction");
+ }
+ mScreenshotHandler.resetTimeout();
+ }
+
+ @Override
+ public void onDismiss() {
+ finishDismiss();
+ }
+
+ @Override
+ public void onTouchOutside() {
+ // TODO(159460485): Remove this when focus is handled properly in the system
+ setWindowFocusable(false);
+ }
+ });
+
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "setContentView: " + mViewProxy.getView());
+ }
+ mWindow.setContentView(mViewProxy.getView());
+ }
+
+ private void enqueueScrollCaptureRequest(UUID requestId, UserHandle owner) {
+ // Wait until this window is attached to request because it is
+ // the reference used to locate the target window (below).
+ withWindowAttached(() -> {
+ requestScrollCapture(requestId, owner);
+ mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
+ new ViewRootImpl.ActivityConfigCallback() {
+ @Override
+ public void onConfigurationChanged(Configuration overrideConfig,
+ int newDisplayId) {
+ if (mConfigChanges.applyNewConfig(mContext.getResources())) {
+ // Hide the scroll chip until we know it's available in this
+ // orientation
+ mActionsController.onScrollChipInvalidated();
+ // Delay scroll capture eval a bit to allow the underlying activity
+ // to set up in the new orientation.
+ mScreenshotHandler.postDelayed(
+ () -> requestScrollCapture(requestId, owner), 150);
+ mViewProxy.updateInsets(
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
+ // Screenshot animation calculations won't be valid anymore,
+ // so just end
+ if (mScreenshotAnimation != null
+ && mScreenshotAnimation.isRunning()) {
+ mScreenshotAnimation.end();
+ }
+ }
+ }
+
+ @Override
+ public void requestCompatCameraControl(boolean showControl,
+ boolean transformationApplied,
+ ICompatCameraControlCallback callback) {
+ Log.w(TAG, "Unexpected requestCompatCameraControl callback");
+ }
+ });
+ });
+ }
+
+ private void requestScrollCapture(UUID requestId, UserHandle owner) {
+ mScrollCaptureExecutor.requestScrollCapture(
+ mDisplay.getDisplayId(),
+ mWindow.getDecorView().getWindowToken(),
+ (response) -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
+ 0, response.getPackageName());
+ mActionsController.onScrollChipReady(requestId,
+ () -> onScrollButtonClicked(owner, response));
+ return Unit.INSTANCE;
+ }
+ );
+ }
+
+ private void onScrollButtonClicked(UserHandle owner, ScrollCaptureResponse response) {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "scroll chip tapped");
+ }
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
+ response.getPackageName());
+ Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplay.getDisplayId(),
+ getFullScreenRect());
+ if (newScreenshot == null) {
+ Log.e(TAG, "Failed to capture current screenshot for scroll transition!");
+ return;
+ }
+ // delay starting scroll capture to make sure scrim is up before the app moves
+ mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+ mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner));
+ }
+
+ private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
+ mScrollCaptureExecutor.executeBatchScrollCapture(response,
+ () -> {
+ final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent(
+ owner, mContext);
+ mContext.startActivity(intent);
+ },
+ mViewProxy::restoreNonScrollingUi,
+ mViewProxy::startLongScreenshotTransition);
+ }
+
+ private void withWindowAttached(Runnable action) {
+ View decorView = mWindow.getDecorView();
+ if (decorView.isAttachedToWindow()) {
+ action.run();
+ } else {
+ decorView.getViewTreeObserver().addOnWindowAttachListener(
+ new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ mAttachRequested = false;
+ decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
+ action.run();
+ }
+
+ @Override
+ public void onWindowDetached() {
+ }
+ });
+
+ }
+ }
+
+ @MainThread
+ private void attachWindow() {
+ View decorView = mWindow.getDecorView();
+ if (decorView.isAttachedToWindow() || mAttachRequested) {
+ return;
+ }
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "attachWindow");
+ }
+ mAttachRequested = true;
+ mWindowManager.addView(decorView, mWindowLayoutParams);
+ decorView.requestApplyInsets();
+
+ ViewGroup layout = decorView.requireViewById(android.R.id.content);
+ layout.setClipChildren(false);
+ layout.setClipToPadding(false);
+ }
+
+ @Override
+ public void removeWindow() {
+ final View decorView = mWindow.peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "Removing screenshot window");
+ }
+ mWindowManager.removeViewImmediate(decorView);
+ mDetachRequested = false;
+ }
+ if (mAttachRequested && !mDetachRequested) {
+ mDetachRequested = true;
+ withWindowAttached(this::removeWindow);
+ }
+
+ mViewProxy.stopInputListening();
+ }
+
+ private void playCameraSoundIfNeeded() {
+ if (mScreenshotSoundController == null) return;
+ // the controller is not-null only on the default display controller
+ mScreenshotSoundController.playScreenshotSoundAsync();
+ }
+
+ /**
+ * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
+ * failure).
+ */
+ private void saveScreenshotAndToast(ScreenshotData screenshot, Consumer<Uri> finisher) {
+ // Play the shutter sound to notify that we've taken a screenshot
+ playCameraSoundIfNeeded();
+
+ if (screenshotSaveImageExporter()) {
+ saveScreenshotInBackground(screenshot, UUID.randomUUID(), finisher, result -> {
+ if (result.uri != null) {
+ mScreenshotHandler.post(() -> Toast.makeText(mContext,
+ R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
+ }
+ });
+ } else {
+ saveScreenshotInWorkerThread(
+ screenshot.getUserHandle(),
+ /* onComplete */ finisher,
+ /* actionsReadyListener */ imageData -> {
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG,
+ "returning URI to finisher (Consumer<URI>): " + imageData.uri);
+ }
+ finisher.accept(imageData.uri);
+ if (imageData.uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0,
+ mPackageName);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_save_text);
+ } else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
+ mScreenshotHandler.post(() -> Toast.makeText(mContext,
+ R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show());
+ }
+ },
+ null);
+ }
+ }
+
+ /**
+ * Starts the animation after taking the screenshot
+ */
+ private void startAnimation(Rect screenRect, boolean showFlash, Runnable onAnimationComplete) {
+ if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
+ mScreenshotAnimation.cancel();
+ }
+
+ mScreenshotAnimation =
+ mViewProxy.createScreenshotDropInAnimation(screenRect, showFlash);
+ if (onAnimationComplete != null) {
+ mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ onAnimationComplete.run();
+ }
+ });
+ }
+
+ // Play the shutter sound to notify that we've taken a screenshot
+ playCameraSoundIfNeeded();
+
+ if (DEBUG_ANIM) {
+ Log.d(TAG, "starting post-screenshot animation");
+ }
+ mScreenshotAnimation.start();
+ }
+
+ /** Reset screenshot view and then call onCompleteRunnable */
+ private void finishDismiss() {
+ Log.d(TAG, "finishDismiss");
+ mActionsController.endScreenshotSession();
+ mScrollCaptureExecutor.close();
+ if (mCurrentRequestCallback != null) {
+ mCurrentRequestCallback.onFinish();
+ mCurrentRequestCallback = null;
+ }
+ mViewProxy.reset();
+ removeWindow();
+ mScreenshotHandler.cancelTimeout();
+ }
+
+ private void saveScreenshotInBackground(ScreenshotData screenshot, UUID requestId,
+ Consumer<Uri> finisher, Consumer<ImageExporter.Result> onResult) {
+ ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
+ requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(),
+ mDisplay.getDisplayId());
+ future.addListener(() -> {
+ try {
+ ImageExporter.Result result = future.get();
+ Log.d(TAG, "Saved screenshot: " + result);
+ logScreenshotResultStatus(result.uri, screenshot.getUserHandle());
+ onResult.accept(result);
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
+ + "finisher.accept(\"" + result.uri + "\"");
+ }
+ finisher.accept(result.uri);
+ } catch (Exception e) {
+ Log.d(TAG, "Failed to store screenshot", e);
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)");
+ }
+ finisher.accept(null);
+ }
+ }, mMainExecutor);
+ }
+
+ /**
+ * Creates a new worker thread and saves the screenshot to the media store.
+ */
+ private void saveScreenshotInWorkerThread(
+ UserHandle owner,
+ @NonNull Consumer<Uri> finisher,
+ @Nullable SaveImageInBackgroundTask.ActionsReadyListener actionsReadyListener,
+ @Nullable SaveImageInBackgroundTask.QuickShareActionReadyListener
+ quickShareActionsReadyListener) {
+ SaveImageInBackgroundTask.SaveImageInBackgroundData
+ data = new SaveImageInBackgroundTask.SaveImageInBackgroundData();
+ data.image = mScreenBitmap;
+ data.finisher = finisher;
+ data.mActionsReadyListener = actionsReadyListener;
+ data.mQuickShareActionsReadyListener = quickShareActionsReadyListener;
+ data.owner = owner;
+ data.displayId = mDisplay.getDisplayId();
+
+ if (mSaveInBgTask != null) {
+ // just log success/failure for the pre-existing screenshot
+ mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
+ }
+
+ mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter,
+ mScreenshotSmartActions, data,
+ mScreenshotNotificationSmartActionsProvider);
+ mSaveInBgTask.execute();
+ }
+
+ /**
+ * Logs success/failure of the screenshot saving task, and shows an error if it failed.
+ */
+ private void logScreenshotResultStatus(Uri uri, UserHandle owner) {
+ if (uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_save_text);
+ } else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
+ if (mUserManager.isManagedProfile(owner.getIdentifier())) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0,
+ mPackageName);
+ }
+ }
+ }
+
+ /**
+ * Logs success/failure of the screenshot saving task, and shows an error if it failed.
+ */
+ private void logSuccessOnActionsReady(SaveImageInBackgroundTask.SavedImageData imageData) {
+ logScreenshotResultStatus(imageData.uri, imageData.owner);
+ }
+
+ private boolean isUserSetupComplete(UserHandle owner) {
+ return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
+ .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ }
+
+ /**
+ * Updates the window focusability. If the window is already showing, then it updates the
+ * window immediately, otherwise the layout params will be applied when the window is next
+ * shown.
+ */
+ private void setWindowFocusable(boolean focusable) {
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "setWindowFocusable: " + focusable);
+ }
+ int flags = mWindowLayoutParams.flags;
+ if (focusable) {
+ mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ } else {
+ mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+ if (mWindowLayoutParams.flags == flags) {
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "setWindowFocusable: skipping, already " + focusable);
+ }
+ return;
+ }
+ final View decorView = mWindow.peekDecorView();
+ if (decorView != null && decorView.isAttachedToWindow()) {
+ mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+ }
+ }
+
+ private Rect getFullScreenRect() {
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ mDisplay.getRealMetrics(displayMetrics);
+ return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
+ }
+
+ /** Does the aspect ratio of the bitmap with insets removed match the bounds. */
+ private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets,
+ Rect screenBounds) {
+ int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right;
+ int insettedHeight = bitmap.getHeight() - bitmapInsets.top - bitmapInsets.bottom;
+
+ if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
+ || bitmap.getHeight() == 0) {
+ if (DEBUG_UI) {
+ Log.e(TAG, "Provided bitmap and insets create degenerate region: "
+ + bitmap.getWidth() + "x" + bitmap.getHeight() + " " + bitmapInsets);
+ }
+ return false;
+ }
+
+ float insettedBitmapAspect = ((float) insettedWidth) / insettedHeight;
+ float boundsAspect = ((float) screenBounds.width()) / screenBounds.height();
+
+ boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f;
+ if (DEBUG_UI) {
+ Log.d(TAG, "aspectRatiosMatch: don't match bitmap: " + insettedBitmapAspect
+ + ", bounds: " + boundsAspect);
+ }
+ return matchWithinTolerance;
+ }
+
+ /** Injectable factory to create screenshot controller instances for a specific display. */
+ @AssistedFactory
+ public interface Factory extends InteractiveScreenshotHandler.Factory {
+ /**
+ * Creates an instance of the controller for that specific display.
+ *
+ * @param display display to capture
+ */
+ LegacyScreenshotController create(Display display);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 54ae225..9bc3bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -286,7 +286,7 @@
ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
Intent intent = new Intent(context, SmartActionsReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, action.actionIntent)
+ .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT, action.actionIntent)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
@@ -302,9 +302,9 @@
private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
boolean smartActionsEnabled) {
intent
- .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType)
- .putExtra(ScreenshotController.EXTRA_ID, screenshotId)
- .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
+ .putExtra(SmartActionsReceiver.EXTRA_ACTION_TYPE, actionType)
+ .putExtra(SmartActionsReceiver.EXTRA_ID, screenshotId)
+ .putExtra(SmartActionsReceiver.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
}
/**
@@ -327,8 +327,8 @@
}
Intent wrappedIntent = new Intent(mContext, SmartActionsReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT_FILLIN,
+ .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT, quickShare.actionIntent)
+ .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT_FILLIN,
createFillInIntent(uri, imageTime))
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
Bundle extras = quickShare.getExtras();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 0a4635e..653e49f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -95,28 +95,9 @@
/**
* Controls the state and flow for screenshots.
*/
-public class ScreenshotController implements ScreenshotHandler {
+public class ScreenshotController implements InteractiveScreenshotHandler {
private static final String TAG = logTag(ScreenshotController.class);
- public interface TransitionDestination {
- /**
- * Allows the long screenshot activity to call back with a destination location (the bounds
- * on screen of the destination for the transitioning view) and a Runnable to be run once
- * the transition animation is complete.
- */
- void setTransitionDestination(Rect transitionDestination, Runnable onTransitionEnd);
- }
-
- // These strings are used for communicating the action invoked to
- // ScreenshotNotificationSmartActionsProvider.
- public static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
- public static final String EXTRA_ID = "android:screenshot_id";
- public static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
- public static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
- public static final String EXTRA_ACTION_INTENT_FILLIN =
- "android:screenshot_action_intent_fillin";
-
-
// From WizardManagerHelper.java
private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
@@ -378,9 +359,7 @@
if (screenshotPrivateProfileAccessibilityAnnouncementFix()) {
mAnnouncementResolver.getScreenshotAnnouncement(
screenshot.getUserHandle().getIdentifier(),
- announcement -> {
- mViewProxy.announceForAccessibility(announcement);
- });
+ mViewProxy::announceForAccessibility);
} else {
if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
mViewProxy.announceForAccessibility(mContext.getResources().getString(
@@ -413,16 +392,19 @@
* Requests the view to dismiss the current screenshot (may be ignored, if screenshot is already
* being dismissed)
*/
- void requestDismissal(ScreenshotEvent event) {
+ @Override
+ public void requestDismissal(ScreenshotEvent event) {
mViewProxy.requestDismissal(event);
}
- boolean isPendingSharedTransition() {
+ @Override
+ public boolean isPendingSharedTransition() {
return mActionExecutor.isPendingSharedTransition();
}
// Any cleanup needed when the service is being destroyed.
- void onDestroy() {
+ @Override
+ public void onDestroy() {
if (mSaveInBgTask != null) {
// just log success/failure for the pre-existing screenshot
mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
@@ -603,7 +585,8 @@
layout.setClipToPadding(false);
}
- void removeWindow() {
+ @Override
+ public void removeWindow() {
final View decorView = mWindow.peekDecorView();
if (decorView != null && decorView.isAttachedToWindow()) {
if (DEBUG_WINDOW) {
@@ -854,12 +837,12 @@
/** Injectable factory to create screenshot controller instances for a specific display. */
@AssistedFactory
- public interface Factory {
+ public interface Factory extends InteractiveScreenshotHandler.Factory {
/**
* Creates an instance of the controller for that specific display.
*
* @param display display to capture
*/
- ScreenshotController create(Display display);
+ LegacyScreenshotController create(Display display);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index f8b22a6..f902693 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -17,10 +17,6 @@
package com.android.systemui.screenshot;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT_FILLIN;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
import android.app.ActivityOptions;
import android.app.PendingIntent;
@@ -36,6 +32,15 @@
*/
public class SmartActionsReceiver extends BroadcastReceiver {
private static final String TAG = "SmartActionsReceiver";
+ // These strings are used for communicating the action invoked to
+ // ScreenshotNotificationSmartActionsProvider.
+ public static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
+ public static final String EXTRA_ID = "android:screenshot_id";
+ public static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+ public static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+ public static final String EXTRA_ACTION_INTENT_FILLIN =
+ "android:screenshot_action_intent_fillin";
+
private final ScreenshotSmartActions mScreenshotSmartActions;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 07f6e85..50ea3bb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -74,7 +74,7 @@
class TakeScreenshotExecutorImpl
@Inject
constructor(
- private val screenshotControllerFactory: ScreenshotController.Factory,
+ private val interactiveScreenshotHandlerFactory: InteractiveScreenshotHandler.Factory,
displayRepository: DisplayRepository,
@Application private val mainScope: CoroutineScope,
private val screenshotRequestProcessor: ScreenshotRequestProcessor,
@@ -83,7 +83,7 @@
private val headlessScreenshotHandler: HeadlessScreenshotHandler,
) : TakeScreenshotExecutor {
private val displays = displayRepository.displays
- private var screenshotController: ScreenshotController? = null
+ private var screenshotController: InteractiveScreenshotHandler? = null
private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()
/**
@@ -183,7 +183,7 @@
/** Propagates the close system dialog signal to the ScreenshotController. */
override fun onCloseSystemDialogsReceived() {
- if (screenshotController?.isPendingSharedTransition == false) {
+ if (screenshotController?.isPendingSharedTransition() == false) {
screenshotController?.requestDismissal(SCREENSHOT_DISMISSED_OTHER)
}
}
@@ -218,8 +218,9 @@
}
}
- private fun getScreenshotController(display: Display): ScreenshotController {
- val controller = screenshotController ?: screenshotControllerFactory.create(display)
+ private fun getScreenshotController(display: Display): InteractiveScreenshotHandler {
+ val controller =
+ screenshotController ?: interactiveScreenshotHandlerFactory.create(display)
screenshotController = controller
return controller
}
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 8feefa4..9db1f24 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -46,13 +46,17 @@
import android.os.ResultReceiver;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
+import android.widget.ListPopupWindow;
import android.widget.TextView;
import androidx.activity.ComponentActivity;
import androidx.annotation.Nullable;
+import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
@@ -67,6 +71,7 @@
import com.android.systemui.screenshot.scroll.CropView;
import com.android.systemui.settings.UserTracker;
+import java.util.List;
import java.util.Set;
import javax.inject.Inject;
@@ -92,6 +97,7 @@
private static final String TAG = AppClipsActivity.class.getSimpleName();
private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
+ private static final int DRAWABLE_END = 2;
private final AppClipsViewModel.Factory mViewModelFactory;
private final PackageManager mPackageManager;
@@ -192,6 +198,7 @@
mViewModel.getResultLiveData().observe(this, this::setResultThenFinish);
mViewModel.getErrorLiveData().observe(this, this::setErrorThenFinish);
mViewModel.getBacklinksLiveData().observe(this, this::setBacklinksData);
+ mViewModel.mSelectedBacklinksLiveData.observe(this, this::updateBacklinksTextView);
if (savedInstanceState == null) {
int displayId = getDisplayId();
@@ -305,8 +312,8 @@
if (mBacklinksIncludeDataCheckBox.getVisibility() == View.VISIBLE
&& mBacklinksIncludeDataCheckBox.isChecked()
- && mViewModel.getBacklinksLiveData().getValue() != null) {
- ClipData backlinksData = mViewModel.getBacklinksLiveData().getValue().getClipData();
+ && mViewModel.mSelectedBacklinksLiveData.getValue() != null) {
+ ClipData backlinksData = mViewModel.mSelectedBacklinksLiveData.getValue().getClipData();
data.putParcelable(EXTRA_CLIP_DATA, backlinksData);
DebugLogger.INSTANCE.logcatMessage(this,
@@ -330,18 +337,80 @@
finish();
}
- private void setBacklinksData(InternalBacklinksData backlinksData) {
+ private void setBacklinksData(List<InternalBacklinksData> backlinksData) {
mBacklinksIncludeDataCheckBox.setVisibility(View.VISIBLE);
mBacklinksDataTextView.setVisibility(
mBacklinksIncludeDataCheckBox.isChecked() ? View.VISIBLE : View.GONE);
- mBacklinksDataTextView.setText(backlinksData.getClipData().getDescription().getLabel());
+ // Set up the dropdown when multiple backlinks are available.
+ if (backlinksData.size() > 1) {
+ setUpListPopupWindow(backlinksData, mBacklinksDataTextView);
+ }
+ }
+ private void setUpListPopupWindow(List<InternalBacklinksData> backlinksData, View anchor) {
+ ListPopupWindow listPopupWindow = new ListPopupWindow(this);
+ listPopupWindow.setAnchorView(anchor);
+ listPopupWindow.setOverlapAnchor(true);
+ listPopupWindow.setBackgroundDrawable(
+ AppCompatResources.getDrawable(this, R.drawable.backlinks_rounded_rectangle));
+ listPopupWindow.setOnItemClickListener((parent, view, position, id) -> {
+ mViewModel.mSelectedBacklinksLiveData.setValue(backlinksData.get(position));
+ listPopupWindow.dismiss();
+ });
+
+ ArrayAdapter<InternalBacklinksData> adapter = new ArrayAdapter<>(this,
+ R.layout.app_clips_backlinks_drop_down_entry) {
+ @Override
+ public View getView(int position, @Nullable View convertView, ViewGroup parent) {
+ TextView itemView = (TextView) super.getView(position, convertView, parent);
+ InternalBacklinksData data = backlinksData.get(position);
+ itemView.setText(data.getClipData().getDescription().getLabel());
+
+ Drawable icon = data.getAppIcon();
+ icon.setBounds(createBacklinksTextViewDrawableBounds());
+ itemView.setCompoundDrawablesRelative(/* start= */ icon, /* top= */ null,
+ /* end= */ null, /* bottom= */ null);
+
+ return itemView;
+ }
+ };
+ adapter.addAll(backlinksData);
+ listPopupWindow.setAdapter(adapter);
+
+ mBacklinksDataTextView.setOnClickListener(unused -> listPopupWindow.show());
+ }
+
+ /**
+ * Updates the {@link #mBacklinksDataTextView} with the currently selected
+ * {@link InternalBacklinksData}. The {@link AppClipsViewModel#getBacklinksLiveData()} is
+ * expected to be already set when this method is called.
+ */
+ private void updateBacklinksTextView(InternalBacklinksData backlinksData) {
+ mBacklinksDataTextView.setText(backlinksData.getClipData().getDescription().getLabel());
Drawable appIcon = backlinksData.getAppIcon();
- int size = getResources().getDimensionPixelSize(R.dimen.appclips_backlinks_icon_size);
- appIcon.setBounds(/* left= */ 0, /* top= */ 0, /* right= */ size, /* bottom= */ size);
+ Rect compoundDrawableBounds = createBacklinksTextViewDrawableBounds();
+ appIcon.setBounds(compoundDrawableBounds);
+
+ // Try to reuse the dropdown down arrow icon if available, will be null if never set.
+ Drawable dropDownIcon = mBacklinksDataTextView.getCompoundDrawablesRelative()[DRAWABLE_END];
+ if (mViewModel.getBacklinksLiveData().getValue().size() > 1 && dropDownIcon == null) {
+ // Set up the dropdown down arrow drawable only if it is required.
+ dropDownIcon = AppCompatResources.getDrawable(this, R.drawable.arrow_pointing_down);
+ dropDownIcon.setBounds(compoundDrawableBounds);
+ dropDownIcon.setTint(Utils.getColorAttr(this,
+ android.R.attr.textColorSecondary).getDefaultColor());
+ }
+
mBacklinksDataTextView.setCompoundDrawablesRelative(/* start= */ appIcon, /* top= */
- null, /* end= */ null, /* bottom= */ null);
+ null, /* end= */ dropDownIcon, /* bottom= */ null);
+ }
+
+ private Rect createBacklinksTextViewDrawableBounds() {
+ int size = getResources().getDimensionPixelSize(R.dimen.appclips_backlinks_icon_size);
+ Rect bounds = new Rect();
+ bounds.set(/* left= */ 0, /* top= */ 0, /* right= */ size, /* bottom= */ size);
+ return bounds;
}
private void setError(int errorCode) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
index bd9e295..3530b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -21,12 +21,11 @@
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
import static android.content.Intent.CATEGORY_LAUNCHER;
-import static com.google.common.util.concurrent.Futures.withTimeout;
-
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IActivityTaskManager;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.app.assist.AssistContent;
import android.content.ClipData;
@@ -41,7 +40,6 @@
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
@@ -94,7 +92,8 @@
private final MutableLiveData<Bitmap> mScreenshotLiveData;
private final MutableLiveData<Uri> mResultLiveData;
private final MutableLiveData<Integer> mErrorLiveData;
- private final MutableLiveData<InternalBacklinksData> mBacklinksLiveData;
+ private final MutableLiveData<List<InternalBacklinksData>> mBacklinksLiveData;
+ final MutableLiveData<InternalBacklinksData> mSelectedBacklinksLiveData;
private AppClipsViewModel(AppClipsCrossProcessHelper appClipsCrossProcessHelper,
ImageExporter imageExporter, IActivityTaskManager atmService,
@@ -112,6 +111,7 @@
mResultLiveData = new MutableLiveData<>();
mErrorLiveData = new MutableLiveData<>();
mBacklinksLiveData = new MutableLiveData<>();
+ mSelectedBacklinksLiveData = new MutableLiveData<>();
}
/**
@@ -135,10 +135,11 @@
/**
* Triggers the Backlinks flow which:
* <ul>
- * <li>Evaluates the task to query.
- * <li>Requests {@link AssistContent} from that task.
- * <li>Transforms the {@link AssistContent} into {@link ClipData} for Backlinks.
- * <li>The {@link ClipData} is reported to activity via {@link #getBacklinksLiveData()}.
+ * <li>Evaluates the tasks to query.
+ * <li>Requests {@link AssistContent} from all valid tasks.
+ * <li>Transforms {@link AssistContent} into {@link InternalBacklinksData} for Backlinks.
+ * <li>The {@link InternalBacklinksData}s are reported to activity via
+ * {@link #getBacklinksLiveData()}.
* </ul>
*
* @param taskIdsToIgnore id of the tasks to ignore when querying for {@link AssistContent}
@@ -146,24 +147,24 @@
*/
void triggerBacklinks(Set<Integer> taskIdsToIgnore, int displayId) {
DebugLogger.INSTANCE.logcatMessage(this, () -> "Backlinks triggered");
- mBgExecutor.execute(() -> {
- ListenableFuture<InternalBacklinksData> backlinksData = getBacklinksData(
- taskIdsToIgnore, displayId);
- Futures.addCallback(backlinksData, new FutureCallback<>() {
- @Override
- public void onSuccess(@Nullable InternalBacklinksData result) {
- if (result != null) {
- mBacklinksLiveData.setValue(result);
- }
+ ListenableFuture<List<InternalBacklinksData>> backlinksData = getAllAvailableBacklinks(
+ taskIdsToIgnore, displayId);
+ Futures.addCallback(backlinksData, new FutureCallback<>() {
+ @Override
+ public void onSuccess(@Nullable List<InternalBacklinksData> result) {
+ if (result != null && !result.isEmpty()) {
+ // Set the list of backlinks before setting the selected backlink as this is
+ // required when updating the backlink data text view.
+ mBacklinksLiveData.setValue(result);
+ mSelectedBacklinksLiveData.setValue(result.get(0));
}
+ }
- @Override
- public void onFailure(Throwable t) {
- Log.e(TAG, "Error querying for Backlinks data", t);
- }
- }, mMainExecutor);
-
- });
+ @Override
+ public void onFailure(Throwable t) {
+ Log.e(TAG, "Error querying for Backlinks data", t);
+ }
+ }, mMainExecutor);
}
/** Returns a {@link LiveData} that holds the captured screenshot. */
@@ -184,8 +185,11 @@
return mErrorLiveData;
}
- /** Returns a {@link LiveData} that holds Backlinks data in {@link InternalBacklinksData}. */
- LiveData<InternalBacklinksData> getBacklinksLiveData() {
+ /**
+ * Returns a {@link LiveData} that holds all the available Backlinks data and the currently
+ * selected index for displaying the Backlinks in the UI.
+ */
+ LiveData<List<InternalBacklinksData>> getBacklinksLiveData() {
return mBacklinksLiveData;
}
@@ -230,26 +234,58 @@
return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
}
- private ListenableFuture<InternalBacklinksData> getBacklinksData(Set<Integer> taskIdsToIgnore,
- int displayId) {
- return getAllRootTaskInfosOnDisplay(displayId)
- .stream()
- .filter(taskInfo -> shouldIncludeTask(taskInfo, taskIdsToIgnore))
- .findFirst()
- .map(this::getBacklinksDataForTaskId)
- .orElse(Futures.immediateFuture(null));
+ private ListenableFuture<List<InternalBacklinksData>> getAllAvailableBacklinks(
+ Set<Integer> taskIdsToIgnore, int displayId) {
+ ListenableFuture<List<TaskInfo>> allTasksOnDisplayFuture = getAllTasksOnDisplay(displayId);
+
+ ListenableFuture<List<ListenableFuture<InternalBacklinksData>>> backlinksNestedListFuture =
+ Futures.transform(allTasksOnDisplayFuture, allTasksOnDisplay ->
+ allTasksOnDisplay
+ .stream()
+ .filter(taskInfo -> shouldIncludeTask(taskInfo, taskIdsToIgnore))
+ .map(this::getBacklinksDataForTaskInfo)
+ .toList(),
+ mBgExecutor);
+
+ return Futures.transformAsync(backlinksNestedListFuture, Futures::allAsList, mBgExecutor);
}
- private List<RootTaskInfo> getAllRootTaskInfosOnDisplay(int displayId) {
- try {
- return mAtmService.getAllRootTaskInfosOnDisplay(displayId);
- } catch (RemoteException e) {
- Log.e(TAG, String.format("Error while querying for tasks on display %d", displayId), e);
- return Collections.emptyList();
- }
+ /**
+ * Returns all tasks on a given display after querying {@link IActivityTaskManager} from the
+ * {@link #mBgExecutor}.
+ */
+ private ListenableFuture<List<TaskInfo>> getAllTasksOnDisplay(int displayId) {
+ SettableFuture<List<TaskInfo>> recentTasksFuture = SettableFuture.create();
+ mBgExecutor.execute(() -> {
+ try {
+ // Directly call into ActivityTaskManagerService instead of going through WMShell
+ // because WMShell is only available in the main SysUI process and App Clips runs
+ // in its own separate process as it deals with bitmaps.
+ List<TaskInfo> allTasksOnDisplay = mAtmService.getTasks(
+ /* maxNum= */ Integer.MAX_VALUE,
+ // PIP tasks are not visible in recents. So _not_ filtering for
+ // tasks that are only visible in recents.
+ /* filterOnlyVisibleRecents= */ false,
+ /* keepIntentExtra= */ false,
+ displayId)
+ .stream()
+ .map(runningTaskInfo -> (TaskInfo) runningTaskInfo)
+ .toList();
+ recentTasksFuture.set(allTasksOnDisplay);
+ } catch (Exception e) {
+ Log.e(TAG, String.format("Error getting all tasks on displayId %d", displayId), e);
+ recentTasksFuture.set(Collections.emptyList());
+ }
+ });
+
+ return withTimeout(recentTasksFuture);
}
- private boolean shouldIncludeTask(RootTaskInfo taskInfo, Set<Integer> taskIdsToIgnore) {
+ /**
+ * Returns whether the app represented by the provided {@link TaskInfo} should be included for
+ * querying for {@link AssistContent}.
+ */
+ private boolean shouldIncludeTask(TaskInfo taskInfo, Set<Integer> taskIdsToIgnore) {
DebugLogger.INSTANCE.logcatMessage(this,
() -> String.format("shouldIncludeTask taskId %d; topActivity %s", taskInfo.taskId,
taskInfo.topActivity));
@@ -262,11 +298,14 @@
&& taskInfo.numActivities > 0
&& taskInfo.topActivity != null
&& taskInfo.topActivityInfo != null
- && taskInfo.childTaskIds.length > 0
&& taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_STANDARD
&& canAppStartThroughLauncher(taskInfo.topActivity.getPackageName());
}
+ /**
+ * Returns whether the app represented by the provided {@code packageName} can be launched
+ * through the all apps tray by a user.
+ */
private boolean canAppStartThroughLauncher(String packageName) {
// Use Intent.resolveActivity API to check if the intent resolves as that is what Android
// uses internally when apps use Context.startActivity.
@@ -274,8 +313,12 @@
!= null;
}
- private ListenableFuture<InternalBacklinksData> getBacklinksDataForTaskId(
- RootTaskInfo taskInfo) {
+ /**
+ * Returns an {@link InternalBacklinksData} that represents the Backlink data internally, which
+ * is captured by querying the system using {@link TaskInfo#taskId}.
+ */
+ private ListenableFuture<InternalBacklinksData> getBacklinksDataForTaskInfo(
+ TaskInfo taskInfo) {
DebugLogger.INSTANCE.logcatMessage(this,
() -> String.format("getBacklinksDataForTaskId for taskId %d; topActivity %s",
taskInfo.taskId, taskInfo.topActivity));
@@ -284,7 +327,13 @@
int taskId = taskInfo.taskId;
mAssistContentRequester.requestAssistContent(taskId, assistContent ->
backlinksData.set(getBacklinksDataFromAssistContent(taskInfo, assistContent)));
- return withTimeout(backlinksData, 5L, TimeUnit.SECONDS, newSingleThreadScheduledExecutor());
+ return withTimeout(backlinksData);
+ }
+
+ /** Returns the same {@link ListenableFuture} but with a 5 {@link TimeUnit#SECONDS} timeout. */
+ private static <V> ListenableFuture<V> withTimeout(ListenableFuture<V> future) {
+ return Futures.withTimeout(future, 5L, TimeUnit.SECONDS,
+ newSingleThreadScheduledExecutor());
}
/**
@@ -306,7 +355,7 @@
* @param content the {@link AssistContent} to map into Backlinks {@link ClipData}.
* @return {@link InternalBacklinksData} that represents the Backlinks data along with app icon.
*/
- private InternalBacklinksData getBacklinksDataFromAssistContent(RootTaskInfo taskInfo,
+ private InternalBacklinksData getBacklinksDataFromAssistContent(TaskInfo taskInfo,
@Nullable AssistContent content) {
DebugLogger.INSTANCE.logcatMessage(this,
() -> String.format("getBacklinksDataFromAssistContent taskId %d; topActivity %s",
@@ -365,7 +414,7 @@
return resolvedComponent.getPackageName().equals(requiredPackageName);
}
- private String getAppNameOfTask(RootTaskInfo taskInfo) {
+ private String getAppNameOfTask(TaskInfo taskInfo) {
return taskInfo.topActivityInfo.loadLabel(mPackageManager).toString();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 682f848..254dde4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -16,12 +16,17 @@
package com.android.systemui.screenshot.dagger;
+import static com.android.systemui.Flags.screenshotUiControllerRefactor;
+
import android.app.Service;
import android.view.accessibility.AccessibilityManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
+import com.android.systemui.screenshot.InteractiveScreenshotHandler;
+import com.android.systemui.screenshot.LegacyScreenshotController;
+import com.android.systemui.screenshot.ScreenshotController;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
import com.android.systemui.screenshot.ScreenshotSoundController;
@@ -90,4 +95,15 @@
AccessibilityManager accessibilityManager) {
return new ScreenshotViewModel(accessibilityManager);
}
+
+ @Provides
+ static InteractiveScreenshotHandler.Factory providesScreenshotController(
+ LegacyScreenshotController.Factory legacyScreenshotController,
+ ScreenshotController.Factory screenshotController) {
+ if (screenshotUiControllerRefactor()) {
+ return screenshotController;
+ } else {
+ return legacyScreenshotController;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
index ebac5bf..08c1fca 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotData.java
@@ -16,8 +16,9 @@
package com.android.systemui.screenshot.scroll;
+import android.graphics.Rect;
+
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.screenshot.ScreenshotController;
import java.util.concurrent.atomic.AtomicReference;
@@ -30,9 +31,18 @@
@SysUISingleton
public class LongScreenshotData {
private final AtomicReference<ScrollCaptureController.LongScreenshot> mLongScreenshot;
- private final AtomicReference<ScreenshotController.TransitionDestination>
+ private final AtomicReference<TransitionDestination>
mTransitionDestinationCallback;
+ public interface TransitionDestination {
+ /**
+ * Allows the long screenshot activity to call back with a destination location (the bounds
+ * on screen of the destination for the transitioning view) and a Runnable to be run once
+ * the transition animation is complete.
+ */
+ void setTransitionDestination(Rect transitionDestination, Runnable onTransitionEnd);
+ }
+
@Inject
public LongScreenshotData() {
mLongScreenshot = new AtomicReference<>();
@@ -63,15 +73,14 @@
/**
* Set the holder's TransitionDestination callback.
*/
- public void setTransitionDestinationCallback(
- ScreenshotController.TransitionDestination destination) {
+ public void setTransitionDestinationCallback(TransitionDestination destination) {
mTransitionDestinationCallback.set(destination);
}
/**
* Return the current TransitionDestination callback and clear.
*/
- public ScreenshotController.TransitionDestination takeTransitionDestinationCallback() {
+ public TransitionDestination takeTransitionDestinationCallback() {
return mTransitionDestinationCallback.getAndSet(null);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index b468d0e..05c50fe 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -39,6 +39,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Flags
import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
@@ -132,13 +133,6 @@
private var touchMonitor: TouchMonitor? = null
/**
- * The width of the area in which a right edge swipe can open the hub, in pixels. Read from
- * resources when [initView] is called.
- */
- // TODO(b/320786721): support RTL layouts
- private var rightEdgeSwipeRegionWidth: Int = 0
-
- /**
* True if we are currently tracking a touch intercepted by the hub, either because the hub is
* open or being opened.
*/
@@ -264,11 +258,6 @@
communalContainerView = containerView
- rightEdgeSwipeRegionWidth =
- containerView.resources.getDimensionPixelSize(
- R.dimen.communal_right_edge_swipe_region_width
- )
-
val topEdgeSwipeRegionWidth =
containerView.resources.getDimensionPixelSize(
R.dimen.communal_top_edge_swipe_region_height
@@ -285,7 +274,7 @@
// Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not
// occluded.
lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) {
- // Avoid adding exclusion to right/left edges to allow back gestures.
+ // Avoid adding exclusion to end/start edges to allow back gestures.
val insets =
if (glanceableHubBackGesture()) {
containerView.rootWindowInsets.getInsets(WindowInsets.Type.systemGestures())
@@ -293,25 +282,38 @@
Insets.NONE
}
- containerView.systemGestureExclusionRects =
- listOf(
- // Only allow swipe up to bouncer and swipe down to shade in the very
- // top/bottom to avoid conflicting with widgets in the hub grid.
- Rect(
- insets.left,
- topEdgeSwipeRegionWidth,
- containerView.right - insets.right,
- containerView.bottom - bottomEdgeSwipeRegionWidth
- ),
- // Disable back gestures on the left side of the screen, to avoid
- // conflicting with scene transitions.
- Rect(
- 0,
- 0,
- insets.right,
- containerView.bottom,
- )
+ val ltr = containerView.layoutDirection == View.LAYOUT_DIRECTION_LTR
+
+ val backGestureInset =
+ Rect(
+ if (ltr) 0 else insets.left,
+ 0,
+ if (ltr) insets.right else containerView.right,
+ containerView.bottom,
)
+
+ containerView.systemGestureExclusionRects =
+ if (Flags.hubmodeFullscreenVerticalSwipe()) {
+ listOf(
+ // Disable back gestures on the left side of the screen, to avoid
+ // conflicting with scene transitions.
+ backGestureInset
+ )
+ } else {
+ listOf(
+ // Only allow swipe up to bouncer and swipe down to shade in the very
+ // top/bottom to avoid conflicting with widgets in the hub grid.
+ Rect(
+ insets.left,
+ topEdgeSwipeRegionWidth,
+ containerView.right - insets.right,
+ containerView.bottom - bottomEdgeSwipeRegionWidth
+ ),
+ // Disable back gestures on the left side of the screen, to avoid
+ // conflicting with scene transitions.
+ backGestureInset
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index bc5cf2a..b60c193 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -41,10 +41,10 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
import com.android.systemui.biometrics.AuthController;
@@ -101,7 +101,7 @@
private final Context mContext;
private final WindowRootViewComponent.Factory mWindowRootViewComponentFactory;
- private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mWindowManager;
private final IActivityManager mActivityManager;
private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
@@ -145,7 +145,7 @@
public NotificationShadeWindowControllerImpl(
Context context,
WindowRootViewComponent.Factory windowRootViewComponentFactory,
- WindowManager windowManager,
+ ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
IActivityManager activityManager,
DozeParameters dozeParameters,
StatusBarStateController statusBarStateController,
@@ -165,7 +165,7 @@
Lazy<CommunalInteractor> communalInteractor) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
- mWindowManager = windowManager;
+ mWindowManager = viewCaptureAwareWindowManager;
mActivityManager = activityManager;
mDozeParameters = dozeParameters;
mKeyguardStateController = keyguardStateController;
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 2b2aac64..f90dd3c 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
@@ -24,8 +24,8 @@
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.activatable.Activatable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -41,22 +41,23 @@
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/** Models UI state and handles user input for the shade scene. */
@SysUISingleton
class ShadeSceneViewModel
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
val qsSceneAdapter: QSSceneAdapter,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val brightnessMirrorViewModel: BrightnessMirrorViewModel,
@@ -66,42 +67,61 @@
private val footerActionsController: FooterActionsController,
private val sceneInteractor: SceneInteractor,
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
-) {
- val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+) : Activatable {
+ val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
combine(
- shadeInteractor.shadeMode,
- qsSceneAdapter.isCustomizerShowing,
- ) { shadeMode, isCustomizerShowing ->
- destinationScenes(
- shadeMode = shadeMode,
- isCustomizing = isCustomizerShowing,
- )
+ shadeInteractor.shadeMode,
+ qsSceneAdapter.isCustomizerShowing,
+ ) { shadeMode, isCustomizerShowing ->
+ buildMap {
+ if (!isCustomizerShowing) {
+ set(
+ Swipe(SwipeDirection.Up),
+ UserActionResult(
+ SceneFamilies.Home,
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+ )
+ )
+ }
+
+ // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
+ if (shadeMode is ShadeMode.Single) {
+ set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
+ }
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue =
- destinationScenes(
- shadeMode = shadeInteractor.shadeMode.value,
- isCustomizing = qsSceneAdapter.isCustomizerShowing.value,
- ),
- )
+ }
private val upDestinationSceneKey: Flow<SceneKey?> =
destinationScenes.map { it[Swipe(SwipeDirection.Up)]?.toScene }
+ private val _isClickable = MutableStateFlow(false)
/** Whether or not the shade container should be clickable. */
- val isClickable: StateFlow<Boolean> =
- upDestinationSceneKey
- .flatMapLatestConflated { key ->
- key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null)
+ val isClickable: StateFlow<Boolean> = _isClickable.asStateFlow()
+
+ /**
+ * Activates the view-model.
+ *
+ * Serves as an entrypoint to kick off coroutine work that the view-model requires in order to
+ * keep its state fresh and/or perform side-effects.
+ *
+ * Suspends the caller forever as it will keep doing work until canceled.
+ *
+ * **Must be invoked** when the scene becomes the current scene or when it becomes visible
+ * during a transition (the choice is the responsibility of the parent). Similarly, the work
+ * must be canceled when the scene stops being visible or the current scene.
+ */
+ override suspend fun activate() {
+ coroutineScope {
+ launch {
+ upDestinationSceneKey
+ .flatMapLatestConflated { key ->
+ key?.let { sceneInteractor.resolveSceneFamily(key) } ?: flowOf(null)
+ }
+ .map { it == Scenes.Lockscreen }
+ .collectLatest { _isClickable.value = it }
}
- .map { it == Scenes.Lockscreen }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false
- )
+ }
+ }
val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
@@ -132,24 +152,4 @@
}
return footerActionsViewModelFactory.create(lifecycleOwner)
}
-
- private fun destinationScenes(
- shadeMode: ShadeMode,
- isCustomizing: Boolean,
- ): Map<UserAction, UserActionResult> {
- return buildMap {
- if (!isCustomizing) {
- set(
- Swipe(SwipeDirection.Up),
- UserActionResult(
- SceneFamilies.Home,
- ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- )
- )
- } // TODO(b/330200163) Add an else to be able to collapse the shade while customizing
- if (shadeMode is ShadeMode.Single) {
- set(Swipe(SwipeDirection.Down), UserActionResult(Scenes.QuickSettings))
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 0e3f775..59fd0ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -105,8 +105,6 @@
}
companion object {
- // TODO(b/354930838): Use the icon from the call notification instead of hard-coding the
- // icon to always be a phone.
private val phoneIcon =
Icon.Resource(
com.android.internal.R.drawable.ic_phone,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
index a6605f6..a621b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -18,12 +18,9 @@
import android.annotation.SuppressLint
import android.app.NotificationManager
-import android.os.UserHandle
-import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -43,23 +40,16 @@
import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printCollection
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
/**
@@ -73,12 +63,10 @@
class LockScreenMinimalismCoordinator
@Inject
constructor(
- @Background private val bgDispatcher: CoroutineDispatcher,
private val dumpManager: DumpManager,
private val headsUpInteractor: HeadsUpNotificationInteractor,
private val logger: LockScreenMinimalismCoordinatorLogger,
@Application private val scope: CoroutineScope,
- private val secureSettings: SecureSettings,
private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
private val shadeInteractor: ShadeInteractor,
@@ -147,29 +135,7 @@
if (NotificationMinimalismPrototype.isEnabled) {
return flowOf(true)
}
- return secureSettings
- // emit whenever the setting has changed
- .observerFlow(
- UserHandle.USER_ALL,
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- )
- // perform a query immediately
- .onStart { emit(Unit) }
- // for each change, lookup the new value
- .map {
- secureSettings.getIntForUser(
- name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- def = 0,
- userHandle = UserHandle.USER_CURRENT,
- ) == 1
- }
- // don't emit anything if nothing has changed
- .distinctUntilChanged()
- // perform lookups on the bg thread pool
- .flowOn(bgDispatcher)
- // only track the most recent emission, if events are happening faster than they can be
- // consumed
- .conflate()
+ return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
}
private suspend fun trackUnseenFilterSettingChanges() {
@@ -177,6 +143,7 @@
// update local field and invalidate if necessary
if (isSettingEnabled != unseenFilterEnabled) {
unseenFilterEnabled = isSettingEnabled
+ unseenNotifications.clear()
unseenNotifPromoter.invalidateList("unseen setting changed")
}
// if the setting is enabled, then start tracking and filtering unseen notifications
@@ -190,21 +157,21 @@
private val collectionListener =
object : NotifCollectionListener {
override fun onEntryAdded(entry: NotificationEntry) {
- if (!isShadeVisible) {
+ if (unseenFilterEnabled && !isShadeVisible) {
logger.logUnseenAdded(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryUpdated(entry: NotificationEntry) {
- if (!isShadeVisible) {
+ if (unseenFilterEnabled && !isShadeVisible) {
logger.logUnseenUpdated(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
- if (unseenNotifications.remove(entry)) {
+ if (unseenFilterEnabled && unseenNotifications.remove(entry)) {
logger.logUnseenRemoved(entry.key)
}
}
@@ -212,6 +179,7 @@
private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
+ if (!unseenFilterEnabled) return
// Only ever elevate a top unseen notification on keyguard, not even locked shade
if (statusBarStateController.state != StatusBarState.KEYGUARD) {
seenNotificationsInteractor.setTopOngoingNotification(null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index 0103fff..bfea2ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -17,12 +17,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.annotation.SuppressLint
-import android.os.UserHandle
-import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -43,12 +40,9 @@
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
import com.android.systemui.util.indentIfPossible
-import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -56,13 +50,10 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
@@ -79,14 +70,12 @@
class OriginalUnseenKeyguardCoordinator
@Inject
constructor(
- @Background private val bgDispatcher: CoroutineDispatcher,
private val dumpManager: DumpManager,
private val headsUpManager: HeadsUpManager,
private val keyguardRepository: KeyguardRepository,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val logger: KeyguardCoordinatorLogger,
@Application private val scope: CoroutineScope,
- private val secureSettings: SecureSettings,
private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
private val sceneInteractor: SceneInteractor,
@@ -268,29 +257,7 @@
// TODO(b/330387368): should this really just be turned off? If so, hide the setting.
return flowOf(false)
}
- return secureSettings
- // emit whenever the setting has changed
- .observerFlow(
- UserHandle.USER_ALL,
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- )
- // perform a query immediately
- .onStart { emit(Unit) }
- // for each change, lookup the new value
- .map {
- secureSettings.getIntForUser(
- name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- def = 0,
- userHandle = UserHandle.USER_CURRENT,
- ) == 1
- }
- // don't emit anything if nothing has changed
- .distinctUntilChanged()
- // perform lookups on the bg thread pool
- .flowOn(bgDispatcher)
- // only track the most recent emission, if events are happening faster than they can be
- // consumed
- .conflate()
+ return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
}
private suspend fun trackUnseenFilterSettingChanges() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 948a3c2..90a05ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -16,21 +16,35 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.os.UserHandle
+import android.provider.Settings
import android.util.IndentingPrintWriter
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.util.printSection
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/** Interactor for business logic associated with the notification stack. */
@SysUISingleton
class SeenNotificationsInteractor
@Inject
constructor(
+ @Background private val bgDispatcher: CoroutineDispatcher,
private val notificationListRepository: ActiveNotificationListRepository,
+ private val secureSettings: SecureSettings,
) {
/** Are any already-seen notifications currently filtered out of the shade? */
val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
@@ -81,4 +95,29 @@
)
}
}
+
+ fun isLockScreenShowOnlyUnseenNotificationsEnabled(): Flow<Boolean> =
+ secureSettings
+ // emit whenever the setting has changed
+ .observerFlow(
+ UserHandle.USER_ALL,
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ )
+ // perform a query immediately
+ .onStart { emit(Unit) }
+ // for each change, lookup the new value
+ .map {
+ secureSettings.getIntForUser(
+ name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ def = 0,
+ userHandle = UserHandle.USER_CURRENT,
+ ) == 1
+ }
+ // don't emit anything if nothing has changed
+ .distinctUntilChanged()
+ // perform lookups on the bg thread pool
+ .flowOn(bgDispatcher)
+ // only track the most recent emission, if events are happening faster than they can be
+ // consumed
+ .conflate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index a39c487..331d3cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -123,8 +123,6 @@
// Construct the status bar icon view.
val sbIcon = iconBuilder.createIconView(entry)
sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
- val sbChipIcon = iconBuilder.createIconView(entry)
- sbChipIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
// Construct the shelf icon view.
val shelfIcon = iconBuilder.createIconView(entry)
@@ -141,17 +139,9 @@
try {
setIcon(entry, normalIconDescriptor, sbIcon)
- setIcon(entry, normalIconDescriptor, sbChipIcon)
setIcon(entry, sensitiveIconDescriptor, shelfIcon)
setIcon(entry, sensitiveIconDescriptor, aodIcon)
- entry.icons =
- IconPack.buildPack(
- sbIcon,
- sbChipIcon,
- shelfIcon,
- aodIcon,
- entry.icons,
- )
+ entry.icons = IconPack.buildPack(sbIcon, shelfIcon, aodIcon, entry.icons)
} catch (e: InflationException) {
entry.icons = IconPack.buildEmptyPack(entry.icons)
throw e
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
index 82bb5ef..d029ce7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -29,7 +29,6 @@
private final boolean mAreIconsAvailable;
@Nullable private final StatusBarIconView mStatusBarIcon;
- @Nullable private final StatusBarIconView mStatusBarChipIcon;
@Nullable private final StatusBarIconView mShelfIcon;
@Nullable private final StatusBarIconView mAodIcon;
@@ -44,7 +43,7 @@
* haven't been inflated yet or there was an error while inflating them).
*/
public static IconPack buildEmptyPack(@Nullable IconPack fromSource) {
- return new IconPack(false, null, null, null, null, fromSource);
+ return new IconPack(false, null, null, null, fromSource);
}
/**
@@ -52,24 +51,20 @@
*/
public static IconPack buildPack(
@NonNull StatusBarIconView statusBarIcon,
- @NonNull StatusBarIconView statusBarChipIcon,
@NonNull StatusBarIconView shelfIcon,
@NonNull StatusBarIconView aodIcon,
@Nullable IconPack source) {
- return new IconPack(
- true, statusBarIcon, statusBarChipIcon, shelfIcon, aodIcon, source);
+ return new IconPack(true, statusBarIcon, shelfIcon, aodIcon, source);
}
private IconPack(
boolean areIconsAvailable,
@Nullable StatusBarIconView statusBarIcon,
- @Nullable StatusBarIconView statusBarChipIcon,
@Nullable StatusBarIconView shelfIcon,
@Nullable StatusBarIconView aodIcon,
@Nullable IconPack source) {
mAreIconsAvailable = areIconsAvailable;
mStatusBarIcon = statusBarIcon;
- mStatusBarChipIcon = statusBarChipIcon;
mShelfIcon = shelfIcon;
mAodIcon = aodIcon;
if (source != null) {
@@ -84,17 +79,6 @@
}
/**
- * The version of the notification icon that appears inside a chip within the status bar.
- *
- * Separate from {@link #getStatusBarIcon()} so that we don't have to worry about detaching and
- * re-attaching the same view when the chip appears and hides.
- */
- @Nullable
- public StatusBarIconView getStatusBarChipIcon() {
- return mStatusBarChipIcon;
- }
-
- /**
* The version of the icon that appears in the "shelf" at the bottom of the notification shade.
* In general, this icon also appears somewhere on the notification and is "sucked" into the
* shelf as the scrolls beyond it.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 35afda7..9f634be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -89,10 +90,59 @@
inflater.inflate(R.layout.status_bar_notification_row, parent, listenerExecutor, this);
}
+ /**
+ * Inflates a new notificationView synchronously.
+ * This method is only for testing-purpose.
+ */
+ @VisibleForTesting
+ public ExpandableNotificationRow inflateSynchronously(@NonNull Context context,
+ @Nullable ViewGroup parent, @NonNull NotificationEntry entry) {
+ final LayoutInflater inflater = new BasicRowInflater(context);
+ inflater.setFactory2(makeRowInflater(entry));
+ final ExpandableNotificationRow inflate = (ExpandableNotificationRow) inflater.inflate(
+ R.layout.status_bar_notification_row,
+ parent /* root */,
+ false /* attachToRoot */);
+ return inflate;
+ }
+
private RowAsyncLayoutInflater makeRowInflater(NotificationEntry entry) {
return new RowAsyncLayoutInflater(entry, mSystemClock, mLogger);
}
+ /**
+ * A {@link LayoutInflater} that is copy of BasicLayoutInflater.
+ */
+ private static class BasicRowInflater extends LayoutInflater {
+ private static final String[] sClassPrefixList =
+ {"android.widget.", "android.webkit.", "android.app."};
+ BasicRowInflater(Context context) {
+ super(context);
+ }
+
+ @Override
+ public LayoutInflater cloneInContext(Context newContext) {
+ return new BasicRowInflater(newContext);
+ }
+
+ @Override
+ protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+ for (String prefix : sClassPrefixList) {
+ try {
+ View view = createView(name, prefix, attrs);
+ if (view != null) {
+ return view;
+ }
+ } catch (ClassNotFoundException e) {
+ // In this case we want to let the base class take a crack
+ // at it.
+ }
+ }
+
+ return super.onCreateView(name, attrs);
+ }
+ }
+
@VisibleForTesting
public static class RowAsyncLayoutInflater implements AsyncLayoutFactory {
private final NotificationEntry mEntry;
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 1a7bc16..e8a7840 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
@@ -91,6 +91,12 @@
val expandFraction: Flow<Float> = shadeInteractor.anyExpansion.dumpValue("expandFraction")
/**
+ * The amount [0-1] that quick settings has been opened. At 0, the shade may be open or closed;
+ * at 1, the quick settings are open.
+ */
+ val shadeToQsFraction: Flow<Float> = shadeInteractor.qsExpansion.dumpValue("shadeToQsFraction")
+
+ /**
* The amount in px that the notification stack should scroll due to internal expansion. This
* should only happen when a notification expansion hits the bottom of the screen, so it is
* necessary to scroll up to keep expanding the notification.
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 a0d4ca2..ae31151 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -261,8 +261,6 @@
boolean isScreenFullyOff();
- boolean isCameraAllowedByAdmin();
-
boolean isGoingToSleep();
void notifyBiometricAuthModeChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index c8a4450..5209d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -49,6 +49,7 @@
import com.android.systemui.emergency.EmergencyGesture;
import com.android.systemui.emergency.EmergencyGestureModule.EmergencyGestureIntentFactory;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QSPanelController;
@@ -109,6 +110,7 @@
private final Lazy<CameraLauncher> mCameraLauncherLazy;
private final QuickSettingsController mQsController;
private final QSHost mQSHost;
+ private final KeyguardInteractor mKeyguardInteractor;
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -148,6 +150,7 @@
UserTracker userTracker,
QSHost qsHost,
ActivityStarter activityStarter,
+ KeyguardInteractor keyguardInteractor,
EmergencyGestureIntentFactory emergencyGestureIntentFactory) {
mCentralSurfaces = centralSurfaces;
mQsController = quickSettingsController;
@@ -176,7 +179,7 @@
mCameraLauncherLazy = cameraLauncherLazy;
mUserTracker = userTracker;
mQSHost = qsHost;
-
+ mKeyguardInteractor = keyguardInteractor;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
mVibratorOptional, resources);
@@ -351,6 +354,8 @@
}
return;
}
+ mKeyguardInteractor.onCameraLaunchDetected(source);
+
if (!mCentralSurfaces.isDeviceInteractive()) {
mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_CAMERA_LAUNCH,
"com.android.systemui:CAMERA_GESTURE");
@@ -383,6 +388,7 @@
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.reset(true /* hide */);
}
+ mCentralSurfaces.startLaunchTransitionTimeout();
mCameraLauncherLazy.get().launchCamera(source,
mPanelExpansionInteractor.isFullyCollapsed());
mCentralSurfaces.updateScrimController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 88d3e07..d4f2a93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -34,72 +34,127 @@
*/
abstract class CentralSurfacesEmptyImpl : CentralSurfaces {
override val lifecycle = LifecycleRegistry(this)
+
override fun updateIsKeyguard() = false
+
override fun updateIsKeyguard(forceStateChange: Boolean) = false
+
override fun getKeyguardMessageArea(): AuthKeyguardMessageArea? = null
+
override fun isLaunchingActivityOverLockscreen() = false
+
override fun isDismissingShadeForActivityLaunch() = false
+
override fun onKeyguardViewManagerStatesUpdated() {}
+
override fun getCommandQueuePanelsEnabled() = false
+
override fun showWirelessChargingAnimation(batteryLevel: Int) {}
+
override fun checkBarModes() {}
+
override fun updateBubblesVisibility() {}
+
override fun setInteracting(barWindow: Int, interacting: Boolean) {}
+
override fun getDisplayWidth() = 0f
+
override fun getDisplayHeight() = 0f
+
override fun showKeyguard() {}
+
override fun hideKeyguard() = false
+
override fun showKeyguardImpl() {}
+
override fun fadeKeyguardAfterLaunchTransition(
beforeFading: Runnable?,
endRunnable: Runnable?,
cancelRunnable: Runnable?,
) {}
+
override fun startLaunchTransitionTimeout() {}
+
override fun hideKeyguardImpl(forceStateChange: Boolean) = false
+
override fun keyguardGoingAway() {}
+
override fun setKeyguardFadingAway(startTime: Long, delay: Long, fadeoutDuration: Long) {}
+
override fun finishKeyguardFadingAway() {}
+
override fun userActivity() {}
+
override fun endAffordanceLaunch() {}
+
override fun shouldKeyguardHideImmediately() = false
+
override fun showBouncerWithDimissAndCancelIfKeyguard(
performAction: OnDismissAction?,
cancelAction: Runnable?,
) {}
+
override fun getNavigationBarView(): NavigationBarView? = null
+
override fun setBouncerShowing(bouncerShowing: Boolean) {}
+
override fun isScreenFullyOff() = false
- override fun isCameraAllowedByAdmin() = false
+
override fun isGoingToSleep() = false
+
override fun notifyBiometricAuthModeChanged() {}
+
override fun setTransitionToFullShadeProgress(transitionToFullShadeProgress: Float) {}
+
override fun setPrimaryBouncerHiddenFraction(expansion: Float) {}
+
override fun updateScrimController() {}
+
override fun shouldIgnoreTouch() = false
+
override fun isDeviceInteractive() = false
+
override fun handleExternalShadeWindowTouch(event: MotionEvent?) {}
+
override fun handleCommunalHubTouch(event: MotionEvent?) {}
+
override fun awakenDreams() {}
+
override fun isBouncerShowing() = false
+
override fun isBouncerShowingScrimmed() = false
+
override fun updateNotificationPanelTouchState() {}
+
override fun getRotation() = 0
+
override fun setBarStateForTest(state: Int) {}
+
override fun acquireGestureWakeLock(time: Long) {}
+
override fun resendMessage(msg: Int) {}
+
override fun resendMessage(msg: Any?) {}
+
override fun setLastCameraLaunchSource(source: Int) {}
+
override fun setLaunchCameraOnFinishedGoingToSleep(launch: Boolean) {}
+
override fun setLaunchCameraOnFinishedWaking(launch: Boolean) {}
+
override fun setLaunchEmergencyActionOnFinishedGoingToSleep(launch: Boolean) {}
+
override fun setLaunchEmergencyActionOnFinishedWaking(launch: Boolean) {}
+
override fun getQSPanelController(): QSPanelController? = null
+
override fun getDisplayDensity() = 0f
+
override fun setIsLaunchingActivityOverLockscreen(
isLaunchingActivityOverLockscreen: Boolean,
dismissShade: Boolean,
) {}
+
override fun getAnimatorControllerFromNotification(
associatedView: ExpandableNotificationRow?,
): ActivityTransitionAnimator.Controller? = null
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 462ae7a..461a38d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -47,7 +47,6 @@
import android.app.TaskInfo;
import android.app.UiModeManager;
import android.app.WallpaperManager;
-import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -883,8 +882,6 @@
// start old BaseStatusBar.start().
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
- mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
mAccessibilityManager = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
@@ -2627,6 +2624,7 @@
mStackScrollerController.updateSensitivenessForOccludedWakeup();
}
if (mLaunchCameraWhenFinishedWaking) {
+ startLaunchTransitionTimeout();
mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource,
mShadeSurface.isFullyCollapsed());
mLaunchCameraWhenFinishedWaking = false;
@@ -2701,21 +2699,6 @@
}
@Override
- public boolean isCameraAllowedByAdmin() {
- if (mDevicePolicyManager.getCameraDisabled(null,
- mLockscreenUserManager.getCurrentUserId())) {
- return false;
- } else if (mKeyguardStateController.isShowing()
- && mStatusBarKeyguardViewManager.isSecure()) {
- // Check if the admin has disabled the camera specifically for the keyguard
- return (mDevicePolicyManager.getKeyguardDisabledFeatures(null,
- mLockscreenUserManager.getCurrentUserId())
- & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) == 0;
- }
- return true;
- }
-
- @Override
public boolean isGoingToSleep() {
return mWakefulnessLifecycle.getWakefulness()
== WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP;
@@ -2864,7 +2847,6 @@
protected boolean mDeviceInteractive;
- protected DevicePolicyManager mDevicePolicyManager;
private final PowerManager mPowerManager;
protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
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 e08dbb9..b2035e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -389,10 +389,13 @@
// OnReorderingAllowedListener:
private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
- mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
if (NotificationThrottleHun.isEnabled()) {
mAvalancheController.setEnableAtRuntime(true);
+ if (mEntriesToRemoveWhenReorderingAllowed.isEmpty()) {
+ return;
+ }
}
+ mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8f2ad40..41b69a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -802,7 +802,10 @@
hideAlternateBouncer(false);
if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
if (SceneContainerFlag.isEnabled()) {
- mDeviceEntryInteractorLazy.get().attemptDeviceEntry();
+ mSceneInteractorLazy.get().changeScene(
+ Scenes.Bouncer,
+ "primary bouncer requested"
+ );
} else {
mPrimaryBouncerInteractor.show(scrimmed);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 2ea8da8..3898088 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -24,12 +24,8 @@
import android.app.UidObserver
import android.content.Context
import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import androidx.annotation.IdRes
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
-import com.android.settingslib.Utils
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
import com.android.systemui.Flags
@@ -42,7 +38,6 @@
import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
-import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
@@ -120,7 +115,6 @@
Notification.EXTRA_CALL_TYPE,
-1
) == CALL_TYPE_ONGOING,
- entry.icons.statusBarChipIcon,
statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
)
if (newOngoingCallInfo == callNotificationInfo) {
@@ -182,7 +176,8 @@
fun setChipView(chipView: View) {
tearDownChipView()
this.chipView = chipView
- val backgroundView: ChipBackgroundContainer? = chipView.getBackgroundView()
+ val backgroundView: ChipBackgroundContainer? =
+ chipView.findViewById(R.id.ongoing_activity_chip_background)
backgroundView?.maxHeightFetcher = { statusBarWindowController.statusBarHeight }
if (hasOngoingCall()) {
updateChip()
@@ -252,8 +247,6 @@
timeView.setShouldHideText(true)
timeView.stop()
}
-
- updateChipIcon(currentCallNotificationInfo, currentChipView)
updateChipClickListener()
}
@@ -277,38 +270,6 @@
}
}
- private fun updateChipIcon(callInfo: CallNotificationInfo, currentChipView: View) {
- val backgroundView = currentChipView.getBackgroundView() ?: return
- backgroundView.removeView(currentChipView.getIconView())
-
- val iconView = callInfo.iconView ?: return
- with(iconView) {
- id = ICON_ID
- imageTintList = Utils.getColorAttr(context, android.R.attr.colorPrimary)
- // TODO(b/354930838): Update the content description to not include "phone".
- contentDescription =
- context.resources.getString(R.string.ongoing_phone_call_content_description)
- }
-
- val currentParent = iconView.parent as? ViewGroup
- // If we're reinflating the view, we may need to detach the icon view from the
- // old chip before we reattach it to the new one.
- // See also: NotificationIconContainerViewBinder#bindIcons.
- if (currentParent != null && currentParent != backgroundView) {
- currentParent.removeView(iconView)
- currentParent.removeTransientView(iconView)
- }
- backgroundView.addView(iconView, /* index= */ 0, generateIconLayoutParams())
- }
-
- private fun generateIconLayoutParams(): FrameLayout.LayoutParams {
- return FrameLayout.LayoutParams(iconSize, iconSize)
- }
-
- private val iconSize: Int
- get() =
- context.resources.getDimensionPixelSize(R.dimen.ongoing_activity_chip_notif_icon_size)
-
private fun updateChipClickListener() {
if (Flags.statusBarScreenSharingChips()) {
return
@@ -318,7 +279,8 @@
return
}
val currentChipView = chipView
- val backgroundView = currentChipView?.getBackgroundView()
+ val backgroundView =
+ currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background)
val intent = callNotificationInfo?.intent
if (currentChipView != null && backgroundView != null && intent != null) {
currentChipView.setOnClickListener {
@@ -370,14 +332,6 @@
return this.findViewById(R.id.ongoing_activity_chip_time)
}
- private fun View.getBackgroundView(): ChipBackgroundContainer? {
- return this.findViewById(R.id.ongoing_activity_chip_background)
- }
-
- private fun View.getIconView(): View? {
- return this.findViewById(ICON_ID)
- }
-
/**
* If there's an active ongoing call, then we will force the status bar to always show, even if
* the user is in immersive mode. However, we also want to give users the ability to swipe away
@@ -405,8 +359,6 @@
val uid: Int,
/** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
val isOngoing: Boolean,
- /** The view that contains the icon to display in the chip. */
- val iconView: StatusBarIconView?,
/** True if the user has swiped away the status bar while in this phone call. */
val statusBarSwipedAway: Boolean
) {
@@ -512,10 +464,6 @@
}
}
}
-
- companion object {
- @IdRes private val ICON_ID = R.id.ongoing_activity_chip_icon
- }
}
private fun isCallNotification(entry: NotificationEntry): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index a88c6d7..5ba5c06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -38,9 +38,10 @@
class AvalancheController
@Inject
constructor(
- dumpManager: DumpManager,
- private val uiEventLogger: UiEventLogger,
- @Background private val bgHandler: Handler
+ dumpManager: DumpManager,
+ private val uiEventLogger: UiEventLogger,
+ private val headsUpManagerLogger: HeadsUpManagerLogger,
+ @Background private val bgHandler: Handler
) : Dumpable {
private val tag = "AvalancheController"
@@ -53,6 +54,7 @@
// allowed again.
logDroppedHunsInBackground(getWaitingKeys().size)
clearNext()
+ headsUpEntryShowing = null
}
if (field != value) {
field = value
@@ -109,32 +111,36 @@
}
/** Run or delay Runnable for given HeadsUpEntry */
- fun update(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ fun update(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+ val isEnabled = isEnabled()
+ val key = getKey(entry)
+
if (runnable == null) {
- log { "Runnable is NULL, stop update." }
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Runnable NULL, stop")
return
}
- if (!isEnabled()) {
+ if (!isEnabled) {
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key,
+ "NOT ENABLED, run runnable")
runnable.run()
return
}
- log { "\n " }
- val fn = "$label => AvalancheController.update ${getKey(entry)}"
if (entry == null) {
- log { "Entry is NULL, stop update." }
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, "Entry NULL, stop")
return
}
if (debug) {
- debugRunnableLabelMap[runnable] = label
+ debugRunnableLabelMap[runnable] = caller
}
+ var outcome = ""
if (isShowing(entry)) {
- log { "\n$fn => update showing" }
+ outcome = "update showing"
runnable.run()
} else if (entry in nextMap) {
- log { "\n$fn => update next" }
+ outcome = "update next"
nextMap[entry]?.add(runnable)
} else if (headsUpEntryShowing == null) {
- log { "\n$fn => showNow" }
+ outcome = "show now"
showNow(entry, arrayListOf(runnable))
} else {
// Clean up invalid state when entry is in list but not map and vice versa
@@ -156,7 +162,8 @@
)
}
}
- logState("after $fn")
+ outcome += getStateStr()
+ headsUpManagerLogger.logAvalancheUpdate(caller, isEnabled, key, outcome)
}
@VisibleForTesting
@@ -169,32 +176,37 @@
* Run or ignore Runnable for given HeadsUpEntry. If entry was never shown, ignore and delete
* all Runnables associated with that entry.
*/
- fun delete(entry: HeadsUpEntry?, runnable: Runnable?, label: String) {
+ fun delete(entry: HeadsUpEntry?, runnable: Runnable?, caller: String) {
+ val isEnabled = isEnabled()
+ val key = getKey(entry)
+
if (runnable == null) {
- log { "Runnable is NULL, stop delete." }
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key, "Runnable NULL, stop")
return
}
- if (!isEnabled()) {
+ if (!isEnabled) {
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+ "NOT ENABLED, run runnable")
runnable.run()
return
}
- log { "\n " }
- val fn = "$label => AvalancheController.delete " + getKey(entry)
if (entry == null) {
- log { "$fn => entry NULL, running runnable" }
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled, key,
+ "Entry NULL, run runnable")
runnable.run()
return
}
+ var outcome = ""
if (entry in nextMap) {
- log { "$fn => remove from next" }
+ outcome = "remove from next"
if (entry in nextMap) nextMap.remove(entry)
if (entry in nextList) nextList.remove(entry)
uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_REMOVED)
} else if (entry in debugDropSet) {
- log { "$fn => remove from dropset" }
+ outcome = "remove from dropset"
debugDropSet.remove(entry)
} else if (isShowing(entry)) {
- log { "$fn => remove showing ${getKey(entry)}" }
+ outcome = "remove showing"
previousHunKey = getKey(headsUpEntryShowing)
// Show the next HUN before removing this one, so that we don't tell listeners
// onHeadsUpPinnedModeChanged, which causes
@@ -203,23 +215,27 @@
showNext()
runnable.run()
} else {
- log { "$fn => run runnable for untracked shown ${getKey(entry)}" }
+ outcome = "run runnable for untracked shown"
runnable.run()
}
- logState("after $fn")
+ headsUpManagerLogger.logAvalancheDelete(caller, isEnabled(), getKey(entry), outcome)
}
/**
* Returns duration based on
- * 1) Whether HeadsUpEntry is the last one tracked byAvalancheController
+ * 1) Whether HeadsUpEntry is the last one tracked by AvalancheController
* 2) The priority of the top HUN in the next batch Used by
* BaseHeadsUpManager.HeadsUpEntry.calculateFinishTime to shorten display duration.
*/
- fun getDurationMs(entry: HeadsUpEntry, autoDismissMs: Int): Int {
+ fun getDurationMs(entry: HeadsUpEntry?, autoDismissMs: Int): Int {
if (!isEnabled()) {
// Use default duration, like we did before AvalancheController existed
return autoDismissMs
}
+ if (entry == null) {
+ // This should never happen
+ return autoDismissMs
+ }
val showingList: MutableList<HeadsUpEntry> = mutableListOf()
if (headsUpEntryShowing != null) {
showingList.add(headsUpEntryShowing!!)
@@ -384,23 +400,12 @@
}
private fun getStateStr(): String {
- return "SHOWING: [${getKey(headsUpEntryShowing)}]" +
- "\nPREVIOUS: [$previousHunKey]" +
- "\nNEXT LIST: $nextListStr" +
- "\nNEXT MAP: $nextMapStr" +
- "\nDROPPED: $dropSetStr" +
- "\nENABLED: $enableAtRuntime"
- }
-
- private fun logState(reason: String) {
- log {
- "\n================================================================================="
- }
- log { "STATE $reason" }
- log { getStateStr() }
- log {
- "=================================================================================\n"
- }
+ return "\navalanche state:" +
+ "\n\tshowing: [${getKey(headsUpEntryShowing)}]" +
+ "\n\tprevious: [$previousHunKey]" +
+ "\n\tnext list: $nextListStr" +
+ "\n\tnext map: $nextMapStr" +
+ "\n\tdropped: $dropSetStr"
}
private val dropSetStr: String
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 dcd9cae..e23f6ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -344,6 +344,15 @@
}
protected boolean hasFullScreenIntent(@NonNull NotificationEntry entry) {
+ if (entry == null) {
+ return false;
+ }
+ if (entry.getSbn() == null) {
+ return false;
+ }
+ if (entry.getSbn().getNotification() == null) {
+ return false;
+ }
return entry.getSbn().getNotification().fullScreenIntent != null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 6ffb162..80c595f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -66,6 +66,30 @@
})
}
+ fun logAvalancheUpdate(caller: String, isEnabled: Boolean, notifEntryKey: String,
+ outcome: String) {
+ buffer.log(TAG, INFO, {
+ str1 = caller
+ str2 = notifEntryKey
+ str3 = outcome
+ bool1 = isEnabled
+ }, {
+ "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3"
+ })
+ }
+
+ fun logAvalancheDelete(caller: String, isEnabled: Boolean, notifEntryKey: String,
+ outcome: String) {
+ buffer.log(TAG, INFO, {
+ str1 = caller
+ str2 = notifEntryKey
+ str3 = outcome
+ bool1 = isEnabled
+ }, {
+ "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3"
+ })
+ }
+
fun logShowNotification(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
str1 = entry.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index cf9a78f..f693409 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -20,6 +20,7 @@
import android.os.UserManager.DISALLOW_CONFIG_LOCATION
import android.os.UserManager.DISALLOW_MICROPHONE_TOGGLE
import android.os.UserManager.DISALLOW_SHARE_LOCATION
+import com.android.systemui.Flags
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -67,25 +68,18 @@
import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
+import javax.inject.Provider
@Module
interface PolicyModule {
- /** Inject DndTile into tileMap in QSModule */
- @Binds @IntoMap @StringKey(DndTile.TILE_SPEC) fun bindDndTile(dndTile: DndTile): QSTileImpl<*>
-
- /** Inject ModesTile into tileMap in QSModule */
- @Binds
- @IntoMap
- @StringKey(ModesTile.TILE_SPEC)
- fun bindModesTile(modesTile: ModesTile): QSTileImpl<*>
-
/** Inject WorkModeTile into tileMap in QSModule */
@Binds
@IntoMap
@@ -136,7 +130,19 @@
const val CAMERA_TOGGLE_TILE_SPEC = "cameratoggle"
const val MIC_TOGGLE_TILE_SPEC = "mictoggle"
const val DND_TILE_SPEC = "dnd"
- const val MODES_TILE_SPEC = "modes"
+
+ /** Inject DndTile or ModesTile into tileMap in QSModule based on feature flag */
+ @Provides
+ @IntoMap
+ @StringKey(DND_TILE_SPEC)
+ fun bindDndOrModesTile(
+ // Using providers to make sure that the unused tile isn't initialised at all if the
+ // flag is off.
+ dndTile: Provider<DndTile>,
+ modesTile: Provider<ModesTile>,
+ ): QSTileImpl<*> {
+ return if (android.app.Flags.modesUi()) modesTile.get() else dndTile.get()
+ }
/** Inject flashlight config */
@Provides
@@ -386,51 +392,51 @@
return factory.create(MICROPHONE)
}
- /** Inject microphone toggle config */
+ /** Inject DND tile or Modes tile config based on feature flag */
@Provides
@IntoMap
@StringKey(DND_TILE_SPEC)
- fun provideDndTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
- QSTileConfig(
- tileSpec = TileSpec.create(DND_TILE_SPEC),
- uiConfig =
- QSTileUIConfig.Resource(
- iconRes = R.drawable.qs_dnd_icon_off,
- labelRes = R.string.quick_settings_dnd_label,
- ),
- instanceId = uiEventLogger.getNewInstanceId(),
- )
-
- @Provides
- @IntoMap
- @StringKey(MODES_TILE_SPEC)
- fun provideModesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
- QSTileConfig(
- tileSpec = TileSpec.create(MODES_TILE_SPEC),
- uiConfig =
- QSTileUIConfig.Resource(
- iconRes = R.drawable.qs_dnd_icon_off,
- labelRes = R.string.quick_settings_modes_label,
- ),
- instanceId = uiEventLogger.getNewInstanceId(),
- )
+ fun provideDndOrModesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ if (android.app.Flags.modesUi()) {
+ QSTileConfig(
+ tileSpec = TileSpec.create(DND_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_dnd_icon_off,
+ labelRes = R.string.quick_settings_modes_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+ } else {
+ QSTileConfig(
+ tileSpec = TileSpec.create(DND_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_dnd_icon_off,
+ labelRes = R.string.quick_settings_dnd_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+ }
/** Inject ModesTile into tileViewModelMap in QSModule */
@Provides
@IntoMap
- @StringKey(MODES_TILE_SPEC)
+ @StringKey(DND_TILE_SPEC)
fun provideModesTileViewModel(
factory: QSTileViewModelFactory.Static<ModesTileModel>,
mapper: ModesTileMapper,
stateInteractor: ModesTileDataInteractor,
userActionInteractor: ModesTileUserActionInteractor
): QSTileViewModel =
- factory.create(
- TileSpec.create(MODES_TILE_SPEC),
- userActionInteractor,
- stateInteractor,
- mapper,
- )
+ if (android.app.Flags.modesUi() && Flags.qsNewTilesFuture())
+ factory.create(
+ TileSpec.create(DND_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ else StubQSTileViewModel
}
/** Inject FlashlightTile into tileMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
index 5bd26cc..7c1cb6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
@@ -29,7 +29,6 @@
val text: String,
val subtext: String,
val enabled: Boolean,
- val contentDescription: String,
val onClick: () -> Unit,
val onLongClick: () -> Unit,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 7e64090..5ffcb34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -31,6 +31,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.scan
/**
* Viewmodel for the priority ("zen") modes dialog that can be opened from quick settings. It allows
@@ -46,13 +47,32 @@
private val dialogDelegate: ModesDialogDelegate,
) {
// Modes that should be displayed in the dialog
- // TODO(b/346519570): Include modes that have not been set up yet.
private val visibleModes: Flow<List<ZenMode>> =
- zenModeInteractor.modes.map {
- it.filter { mode ->
- mode.rule.isEnabled && (mode.isActive || mode.rule.isManualInvocationAllowed)
+ zenModeInteractor.modes
+ // While this is being collected (or in other words, while the dialog is open), we don't
+ // want a mode to disappear from the list if, for instance, the user deactivates it,
+ // since that can be confusing (similar to how we have visual stability for
+ // notifications while the shade is open).
+ // This ensures new modes are added to the list, and updates to modes already in the
+ // list are registered correctly.
+ .scan(listOf()) { prev, modes ->
+ val prevIds = prev.map { it.id }.toSet()
+
+ modes.filter { mode ->
+ when {
+ // Mode appeared previously -> keep it even if otherwise we may have
+ // filtered it
+ mode.id in prevIds -> true
+ // Mode is enabled -> show if active (so user can toggle off), or if it
+ // can be manually toggled on
+ mode.rule.isEnabled -> mode.isActive || mode.rule.isManualInvocationAllowed
+ // Mode was created as disabled, or disabled by the app that owns it ->
+ // will be shown with a "Set up" text
+ !mode.rule.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
+ else -> false
+ }
+ }
}
- }
val tiles: Flow<List<ModeTileViewModel>> =
visibleModes
@@ -64,30 +84,40 @@
text = mode.rule.name,
subtext = getTileSubtext(mode),
enabled = mode.isActive,
- // TODO(b/346519570): This should be some combination of the above, e.g.
- // "ON: Do Not Disturb, Until Mon 08:09"; see DndTile.
- contentDescription = "",
onClick = {
- if (mode.isActive) {
+ if (!mode.rule.isEnabled) {
+ openSettings(mode)
+ } else if (mode.isActive) {
zenModeInteractor.deactivateMode(mode)
} else {
- // TODO(b/346519570): Handle duration for DND mode.
- zenModeInteractor.activateMode(mode)
+ if (mode.rule.isManualInvocationAllowed) {
+ // TODO(b/346519570): Handle duration for DND mode.
+ zenModeInteractor.activateMode(mode)
+ }
}
},
- onLongClick = {
- val intent: Intent =
- Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
- .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id)
- dialogDelegate.launchFromDialog(intent)
- }
+ onLongClick = { openSettings(mode) }
)
}
}
.flowOn(bgDispatcher)
+ private fun openSettings(mode: ZenMode) {
+ val intent: Intent =
+ Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+ .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id)
+
+ dialogDelegate.launchFromDialog(intent)
+ }
+
private fun getTileSubtext(mode: ZenMode): String {
- // TODO(b/346519570): Use ZenModeConfig.getDescription for manual DND
+ if (!mode.rule.isEnabled) {
+ return context.resources.getString(R.string.zen_mode_set_up)
+ }
+ if (!mode.rule.isManualInvocationAllowed && !mode.isActive) {
+ return context.resources.getString(R.string.zen_mode_no_manual_invocation)
+ }
+
val on = context.resources.getString(R.string.zen_mode_on)
val off = context.resources.getString(R.string.zen_mode_off)
return mode.rule.triggerDescription ?: if (mode.isActive) on else off
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index 4869114..89227cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -24,6 +24,7 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import javax.inject.Inject
@@ -33,6 +34,7 @@
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
/**
@@ -54,12 +56,20 @@
keyguardStatusBarInteractor: KeyguardStatusBarInteractor,
batteryController: BatteryController,
) {
+
+ private val showingHeadsUpStatusBar: Flow<Boolean> =
+ if (NotificationsHeadsUpRefactor.isEnabled) {
+ headsUpNotificationInteractor.showHeadsUpStatusBar
+ } else {
+ flowOf(false)
+ }
+
/** True if this view should be visible and false otherwise. */
val isVisible: StateFlow<Boolean> =
combine(
sceneInteractor.currentScene,
keyguardInteractor.isDozing,
- headsUpNotificationInteractor.showHeadsUpStatusBar,
+ showingHeadsUpStatusBar,
) { currentScene, isDozing, showHeadsUpStatusBar ->
currentScene == Scenes.Lockscreen && !isDozing && !showHeadsUpStatusBar
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index 37833d8..94ff65e 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -21,7 +21,9 @@
import android.graphics.PorterDuffColorFilter
import androidx.activity.compose.BackHandler
import androidx.annotation.StringRes
-import androidx.compose.foundation.background
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -40,6 +42,7 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInteropFilter
@@ -63,6 +66,7 @@
data class TutorialScreenColors(
val backgroundColor: Color,
+ val successBackgroundColor: Color,
val titleColor: Color,
val animationProperties: LottieDynamicProperties
)
@@ -100,6 +104,7 @@
val onTertiaryFixed = LocalAndroidColorScheme.current.onTertiaryFixed
val onTertiaryFixedVariant = LocalAndroidColorScheme.current.onTertiaryFixedVariant
val tertiaryFixedDim = LocalAndroidColorScheme.current.tertiaryFixedDim
+ val surfaceContainer = MaterialTheme.colorScheme.surfaceContainer
val dynamicProperties =
rememberLottieDynamicProperties(
rememberColorFilterProperty(".tertiaryFixedDim", tertiaryFixedDim),
@@ -108,9 +113,10 @@
rememberColorFilterProperty(".onTertiaryFixedVariant", onTertiaryFixedVariant)
)
val screenColors =
- remember(onTertiaryFixed, tertiaryFixedDim, dynamicProperties) {
+ remember(onTertiaryFixed, surfaceContainer, tertiaryFixedDim, dynamicProperties) {
TutorialScreenColors(
backgroundColor = onTertiaryFixed,
+ successBackgroundColor = surfaceContainer,
titleColor = tertiaryFixedDim,
animationProperties = dynamicProperties,
)
@@ -124,11 +130,19 @@
onDoneButtonClicked: () -> Unit,
screenColors: TutorialScreenColors
) {
+ val animatedColor by
+ animateColorAsState(
+ targetValue =
+ if (gestureDone) screenColors.successBackgroundColor
+ else screenColors.backgroundColor,
+ animationSpec = tween(durationMillis = 150, easing = LinearEasing),
+ label = "backgroundColor"
+ )
Column(
verticalArrangement = Arrangement.Center,
modifier =
Modifier.fillMaxSize()
- .background(color = screenColors.backgroundColor)
+ .drawBehind { drawRect(animatedColor) }
.padding(start = 48.dp, top = 124.dp, end = 48.dp, bottom = 48.dp)
) {
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
index 8ab5bc6..169f865 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensorImpl.java
@@ -26,8 +26,8 @@
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
@@ -64,7 +64,7 @@
ThresholdSensor mSecondaryThresholdSensor;
private final DelayableExecutor mDelayableExecutor;
private final Execution mExecution;
- private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>();
+ private final Set<Listener> mListeners = new CopyOnWriteArraySet<>();
private String mTag = null;
@VisibleForTesting protected boolean mPaused;
private ThresholdSensorEvent mLastPrimaryEvent;
@@ -246,7 +246,7 @@
public void unregister(ThresholdSensor.Listener listener) {
mExecution.assertIsMainThread();
mListeners.remove(listener);
- if (mListeners.size() == 0) {
+ if (mListeners.isEmpty()) {
unregisterInternal();
}
}
@@ -296,8 +296,7 @@
}
if (mLastEvent != null) {
ThresholdSensorEvent lastEvent = mLastEvent; // Listeners can null out mLastEvent.
- List<ThresholdSensor.Listener> listeners = new ArrayList<>(mListeners);
- listeners.forEach(proximitySensorListener ->
+ mListeners.forEach(proximitySensorListener ->
proximitySensorListener.onThresholdCrossed(lastEvent));
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt
new file mode 100644
index 0000000..a77acb5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningAction.kt
@@ -0,0 +1,48 @@
+/*
+ * 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
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.content.Context
+import android.content.Intent
+
+/**
+ * label: Notification action label text. intent: The Intent used to start Activity or Broadcast.
+ * isActivity: Defines if the pending intent should start an activity. Default is to broadcast
+ */
+data class CsdWarningAction(
+ val label: String? = null,
+ val intent: Intent? = null,
+ val isActivity: Boolean = false,
+) {
+ fun toPendingIntent(context: Context): PendingIntent? {
+ if (label == null || intent == null) {
+ return null
+ }
+ if (isActivity) {
+ return PendingIntent.getActivity(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ }
+ return PendingIntent.getBroadcast(context, 0, intent, FLAG_IMMUTABLE)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index bb230e6..a63660b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -30,7 +30,6 @@
import android.media.AudioManager;
import android.provider.Settings;
import android.util.Log;
-import android.util.Pair;
import android.view.KeyEvent;
import android.view.WindowManager;
@@ -109,7 +108,7 @@
private long mShowTime;
@VisibleForTesting public int mCachedMediaStreamVolume;
- private Optional<ImmutableList<Pair<String, Intent>>> mActionIntents;
+ private Optional<ImmutableList<CsdWarningAction>> mActionIntents;
private final BroadcastDispatcher mBroadcastDispatcher;
/**
@@ -121,7 +120,7 @@
CsdWarningDialog create(
int csdWarning,
Runnable onCleanup,
- Optional<ImmutableList<Pair<String, Intent>>> actionIntents);
+ Optional<ImmutableList<CsdWarningAction>> actionIntents);
}
@AssistedInject
@@ -132,7 +131,7 @@
NotificationManager notificationManager,
@Background DelayableExecutor delayableExecutor,
@Assisted Runnable onCleanup,
- @Assisted Optional<ImmutableList<Pair<String, Intent>>> actionIntents,
+ @Assisted Optional<ImmutableList<CsdWarningAction>> actionIntents,
BroadcastDispatcher broadcastDispatcher) {
super(context);
mCsdWarning = csdWarning;
@@ -351,39 +350,45 @@
if (Flags.sounddoseCustomization()
&& mActionIntents.isPresent()
&& !mActionIntents.get().isEmpty()) {
- ImmutableList<Pair<String, Intent>> actionIntentsList = mActionIntents.get();
- for (Pair<String, Intent> intentPair : actionIntentsList) {
- if (intentPair != null && intentPair.first != null && intentPair.second != null) {
- PendingIntent pendingActionIntent =
- PendingIntent.getBroadcast(mContext, 0, intentPair.second,
- FLAG_IMMUTABLE);
- builder.addAction(0, intentPair.first, pendingActionIntent);
- // Register receiver to undo volume only when
- // notification conaining the undo action would be sent.
- if (intentPair.first == mContext.getString(R.string.volume_undo_action)) {
- final IntentFilter filterUndo = new IntentFilter(
- VolumeDialog.ACTION_VOLUME_UNDO);
- mBroadcastDispatcher.registerReceiver(mReceiverUndo,
- filterUndo,
- /* executor = default */ null,
- /* user = default */ null,
- Context.RECEIVER_NOT_EXPORTED,
- /* permission = default */ null);
+ ImmutableList<CsdWarningAction> actionIntentsList = mActionIntents.get();
+ for (CsdWarningAction action : actionIntentsList) {
+ if (action.getLabel() == null || action.getIntent() == null) {
+ Log.w(TAG, "Null action intent received. Skipping addition to notification");
+ continue;
+ }
+ PendingIntent pendingActionIntent = action.toPendingIntent(mContext);
+ if (pendingActionIntent == null) {
+ Log.w(TAG, "Null pending intent received. Skipping addition to notification");
+ continue;
+ }
+ builder.addAction(0, action.getLabel(), pendingActionIntent);
- // Register receiver to learn if notification has been dismissed.
- // This is required to unregister receivers to prevent leak.
- Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
- .setPackage(mContext.getPackageName());
- PendingIntent pendingDismissIntent = PendingIntent.getBroadcast(mContext,
- 0, dismissIntent, FLAG_IMMUTABLE);
- mBroadcastDispatcher.registerReceiver(mReceiverDismissNotification,
- new IntentFilter(DISMISS_CSD_NOTIFICATION),
- /* executor = default */ null,
- /* user = default */ null,
- Context.RECEIVER_NOT_EXPORTED,
- /* permission = default */ null);
- builder.setDeleteIntent(pendingDismissIntent);
- }
+ // Register receiver to undo volume only when
+ // notification conaining the undo action would be sent.
+ if (action.getLabel().equals(mContext.getString(R.string.volume_undo_action))) {
+ final IntentFilter filterUndo = new IntentFilter(
+ VolumeDialog.ACTION_VOLUME_UNDO);
+ mBroadcastDispatcher.registerReceiver(mReceiverUndo,
+ filterUndo,
+ /* executor = default */ null,
+ /* user = default */ null,
+ Context.RECEIVER_NOT_EXPORTED,
+ /* permission = default */ null);
+
+ // Register receiver to learn if notification has been dismissed.
+ // This is required to unregister receivers to prevent leak.
+ Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
+ .setPackage(mContext.getPackageName());
+ PendingIntent pendingDismissIntent = PendingIntent.getBroadcast(
+ mContext,
+ 0, dismissIntent, FLAG_IMMUTABLE);
+ mBroadcastDispatcher.registerReceiver(mReceiverDismissNotification,
+ new IntentFilter(DISMISS_CSD_NOTIFICATION),
+ /* executor = default */ null,
+ /* user = default */ null,
+ Context.RECEIVER_NOT_EXPORTED,
+ /* permission = default */ null);
+ builder.setDeleteIntent(pendingDismissIntent);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0770d89..e56f6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -51,7 +51,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -79,7 +78,6 @@
import android.provider.Settings.Global;
import android.text.InputFilter;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
@@ -322,8 +320,8 @@
private final VolumePanelFlag mVolumePanelFlag;
private final VolumeDialogInteractor mInteractor;
// Optional actions for soundDose
- private Optional<ImmutableList<Pair<String, Intent>>> mCsdWarningNotificationActions =
- Optional.of(ImmutableList.of());
+ private Optional<ImmutableList<CsdWarningAction>>
+ mCsdWarningNotificationActions = Optional.of(ImmutableList.of());
public VolumeDialogImpl(
Context context,
@@ -2231,7 +2229,7 @@
}
public void setCsdWarningNotificationActionIntents(
- ImmutableList<Pair<String, Intent>> actionIntent) {
+ ImmutableList<CsdWarningAction> actionIntent) {
mCsdWarningNotificationActions = Optional.of(actionIntent);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index dc2b80c..68d12f6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -16,6 +16,8 @@
package com.android.systemui.volume;
+import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix;
+
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
@@ -26,6 +28,7 @@
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor;
import java.io.PrintWriter;
@@ -41,23 +44,29 @@
private boolean mEnabled;
private final Context mContext;
private VolumeDialogComponent mVolumeComponent;
+ private AudioSharingInteractor mAudioSharingInteractor;
@Inject
- public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) {
+ public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent,
+ AudioSharingInteractor audioSharingInteractor) {
mContext = context;
mVolumeComponent = volumeDialogComponent;
+ mAudioSharingInteractor = audioSharingInteractor;
}
@Override
public void start() {
boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
boolean enableSafetyWarning =
- mContext.getResources().getBoolean(R.bool.enable_safety_warning);
+ mContext.getResources().getBoolean(R.bool.enable_safety_warning);
mEnabled = enableVolumeUi || enableSafetyWarning;
if (!mEnabled) return;
mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning);
setDefaultVolumeController();
+ if (volumeDialogAudioSharingFix()) {
+ mAudioSharingInteractor.handlePrimaryGroupChange();
+ }
}
@Override
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 eb2f71a1..efaca7a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -19,11 +19,13 @@
import android.content.ContentResolver
import android.content.Context
import android.media.AudioManager
+import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
import com.android.settingslib.volume.data.repository.AudioSharingRepository
+import com.android.settingslib.volume.data.repository.AudioSharingRepositoryEmptyImpl
import com.android.settingslib.volume.data.repository.AudioSharingRepositoryImpl
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
@@ -73,19 +75,21 @@
@Provides
@SysUISingleton
fun provideAudioSharingRepository(
- @Application context: Context,
contentResolver: ContentResolver,
localBluetoothManager: LocalBluetoothManager?,
@Application coroutineScope: CoroutineScope,
@Background coroutineContext: CoroutineContext,
): AudioSharingRepository =
- AudioSharingRepositoryImpl(
- context,
- contentResolver,
- localBluetoothManager,
- coroutineScope,
- coroutineContext
- )
+ if (BluetoothUtils.isAudioSharingEnabled() && localBluetoothManager != null) {
+ AudioSharingRepositoryImpl(
+ contentResolver,
+ localBluetoothManager,
+ coroutineScope,
+ coroutineContext
+ )
+ } else {
+ AudioSharingRepositoryEmptyImpl()
+ }
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
index 9f1e60e..1c80887 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioSharingModule.kt
@@ -16,14 +16,14 @@
package com.android.systemui.volume.dagger
-import com.android.settingslib.volume.data.repository.AudioSharingRepository
+import com.android.settingslib.flags.Flags
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractorEmptyImpl
import com.android.systemui.volume.domain.interactor.AudioSharingInteractorImpl
+import dagger.Lazy
import dagger.Module
import dagger.Provides
-import kotlinx.coroutines.CoroutineScope
/** Dagger module for audio sharing code in the volume package */
@Module
@@ -33,8 +33,13 @@
@Provides
@SysUISingleton
fun provideAudioSharingInteractor(
- @Application coroutineScope: CoroutineScope,
- repository: AudioSharingRepository
- ): AudioSharingInteractor = AudioSharingInteractorImpl(coroutineScope, repository)
+ impl: Lazy<AudioSharingInteractorImpl>,
+ emptyImpl: Lazy<AudioSharingInteractorEmptyImpl>,
+ ): AudioSharingInteractor =
+ if (Flags.volumeDialogAudioSharingFix()) {
+ impl.get()
+ } else {
+ emptyImpl.get()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
index aba3015..2170c36 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -17,18 +17,28 @@
package com.android.systemui.volume.domain.interactor
import android.bluetooth.BluetoothCsipSetCoordinator
+import android.media.AudioManager.STREAM_MUSIC
import androidx.annotation.IntRange
import com.android.settingslib.volume.data.repository.AudioSharingRepository
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
+import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
+import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
interface AudioSharingInteractor {
/** Audio sharing secondary headset volume changes. */
@@ -45,6 +55,16 @@
@IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
level: Int
)
+
+ /**
+ * Handle primary group change in audio sharing.
+ *
+ * Once the primary group is changed, we need to sync its volume to STREAM_MUSIC to make sure
+ * the volume adjustment during audio sharing can be kept after the sharing ends.
+ *
+ * TODO(b/355396988) Migrate to audio framework solution once it is in place.
+ */
+ fun handlePrimaryGroupChange()
}
@SysUISingleton
@@ -52,26 +72,60 @@
@Inject
constructor(
@Application private val coroutineScope: CoroutineScope,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
+ private val audioVolumeInteractor: AudioVolumeInteractor,
private val audioSharingRepository: AudioSharingRepository
) : AudioSharingInteractor {
override val volume: Flow<Int?> =
combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) {
- secondaryGroupId,
- volumeMap ->
- if (secondaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) null
- else volumeMap.getOrDefault(secondaryGroupId, DEFAULT_VOLUME)
- }
+ secondaryGroupId,
+ volumeMap ->
+ if (secondaryGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) null
+ else volumeMap.getOrDefault(secondaryGroupId, DEFAULT_VOLUME)
+ }
+ .distinctUntilChanged()
override val volumeMin: Int = AUDIO_SHARING_VOLUME_MIN
override val volumeMax: Int = AUDIO_SHARING_VOLUME_MAX
- override fun setStreamVolume(level: Int) {
+ override fun setStreamVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ level: Int
+ ) {
coroutineScope.launch { audioSharingRepository.setSecondaryVolume(level) }
}
+ override fun handlePrimaryGroupChange() {
+ coroutineScope.launch {
+ audioSharingRepository.primaryGroupId
+ .map { primaryGroupId -> audioSharingRepository.volumeMap.value[primaryGroupId] }
+ .filterNotNull()
+ .distinctUntilChanged()
+ .collect {
+ // Once primary device change, we need to update the STREAM_MUSIC volume to get
+ // align with the primary device's volume
+ setMusicStreamVolume(it)
+ }
+ }
+ }
+
+ private suspend fun setMusicStreamVolume(volume: Int) {
+ withContext(backgroundCoroutineContext) {
+ val musicStream =
+ audioVolumeInteractor.getAudioStream(AudioStream(STREAM_MUSIC)).first()
+ val musicVolume =
+ Math.round(
+ volume.toFloat() * (musicStream.maxVolume - musicStream.minVolume) /
+ (AUDIO_SHARING_VOLUME_MAX - AUDIO_SHARING_VOLUME_MIN)
+ )
+ audioVolumeInteractor.setVolume(AudioStream(STREAM_MUSIC), musicVolume)
+ }
+ }
+
private companion object {
+ const val TAG = "AudioSharingInteractor"
const val DEFAULT_VOLUME = 20
}
}
@@ -82,7 +136,12 @@
override val volumeMin: Int = EMPTY_VOLUME
override val volumeMax: Int = EMPTY_VOLUME
- override fun setStreamVolume(level: Int) {}
+ override fun setStreamVolume(
+ @IntRange(from = AUDIO_SHARING_VOLUME_MIN.toLong(), to = AUDIO_SHARING_VOLUME_MAX.toLong())
+ level: Int
+ ) {}
+
+ override fun handlePrimaryGroupChange() {}
private companion object {
const val EMPTY_VOLUME = 0
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 6dcea14..9df653f 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
@@ -74,7 +74,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -92,6 +91,7 @@
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -1379,6 +1379,28 @@
}
@Test
+ @EnableFlags(FLAG_BP_TALKBACK)
+ fun no_hint_for_talkback_guidance_after_auth() = runGenericTest {
+ val hint by collectLastValue(kosmos.promptViewModel.accessibilityHint)
+
+ kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 0)
+ kosmos.promptViewModel.confirmAuthenticated()
+
+ // Touches should fall outside of sensor area
+ whenever(kosmos.udfpsUtils.getTouchInNativeCoordinates(any(), any(), any()))
+ .thenReturn(Point(0, 0))
+ whenever(kosmos.udfpsUtils.onTouchOutsideOfSensorArea(any(), any(), any(), any(), any()))
+ .thenReturn("Direction")
+
+ kosmos.promptViewModel.onAnnounceAccessibilityHint(
+ obtainMotionEvent(MotionEvent.ACTION_HOVER_ENTER),
+ true
+ )
+
+ assertThat(hint.isNullOrBlank()).isTrue()
+ }
+
+ @Test
@EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
fun descriptionOverriddenByVerticalListContentView() =
runGenericTest(description = "test description", contentView = promptContentView) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
index 63b4ff7..72e0726 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt
@@ -18,6 +18,7 @@
import android.content.res.Configuration
import android.graphics.Rect
+import android.util.LayoutDirection
import android.view.Surface
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -34,6 +35,8 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@ExperimentalCoroutinesApi
@SmallTest
@@ -70,6 +73,28 @@
}
@Test
+ fun directionalDimensionPixelSize() =
+ testScope.runTest {
+ val resourceId = 1001
+ val pixelSize = 501
+ configurationRepository.setDimensionPixelSize(resourceId, pixelSize)
+
+ val config: Configuration = mock()
+ val dimensionPixelSize by
+ collectLastValue(
+ underTest.directionalDimensionPixelSize(LayoutDirection.LTR, resourceId)
+ )
+
+ whenever(config.layoutDirection).thenReturn(LayoutDirection.LTR)
+ configurationRepository.onConfigurationChange(config)
+ assertThat(dimensionPixelSize).isEqualTo(pixelSize)
+
+ whenever(config.layoutDirection).thenReturn(LayoutDirection.RTL)
+ configurationRepository.onConfigurationChange(config)
+ assertThat(dimensionPixelSize).isEqualTo(-pixelSize)
+ }
+
+ @Test
fun dimensionPixelSizes() =
testScope.runTest {
val resourceId1 = 1001
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
similarity index 82%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 7936ccc..c2c94a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -23,6 +23,9 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.LayoutDirection;
import android.view.GestureDetector;
import android.view.MotionEvent;
@@ -68,10 +71,12 @@
AtomicReference reference = new AtomicReference<>(null);
when(mLifecycle.getInternalScopeRef()).thenReturn(reference);
when(mLifecycle.getCurrentState()).thenReturn(Lifecycle.State.CREATED);
+
mTouchHandler = new CommunalTouchHandler(
Optional.of(mCentralSurfaces),
INITIATION_WIDTH,
mKosmos.getCommunalInteractor(),
+ mKosmos.getConfigurationInteractor(),
mLifecycle
);
}
@@ -127,4 +132,26 @@
.onScroll(motionEvent1, motionEvent2, 1, 1))
.isTrue();
}
+
+ @Test
+ public void testTouchInitiationArea() {
+ final int right = 80;
+ final int bottom = 100;
+ final Rect bounds = new Rect(0, 0, right, bottom);
+
+ {
+ final Region region = new Region();
+ mTouchHandler.mLayoutDirectionCallback.accept(LayoutDirection.LTR);
+ mTouchHandler.getTouchInitiationRegion(bounds, region, null);
+ assertThat(region.getBounds()).isEqualTo(
+ new Rect(right - INITIATION_WIDTH, 0, right, bottom));
+ }
+
+ {
+ final Region region = new Region();
+ mTouchHandler.mLayoutDirectionCallback.accept(LayoutDirection.RTL);
+ mTouchHandler.getTouchInitiationRegion(bounds, region, null);
+ assertThat(region.getBounds()).isEqualTo(new Rect(0, 0, INITIATION_WIDTH, bottom));
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index 6c4a730..3388a78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -68,7 +68,7 @@
@Mock private lateinit var systemProperties: SystemPropertiesHelper
@Mock private lateinit var resources: Resources
@Mock private lateinit var restarter: Restarter
- private val userTracker = FakeUserTracker()
+ private lateinit var userTracker: FakeUserTracker
private val flagMap = mutableMapOf<String, Flag<*>>()
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var clearCacheAction: Consumer<String>
@@ -82,6 +82,9 @@
MockitoAnnotations.initMocks(this)
flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
flagMap.put(releasedFlagB.name, releasedFlagB)
+
+ userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockContext })
+
mFeatureFlagsClassicDebug =
FeatureFlagsClassicDebug(
flagManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 0043173..6b60740 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyboard.shortcut.ui.viewmodel
+import android.app.role.RoleManager
+import android.app.role.mockRoleManager
import android.view.KeyEvent
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
@@ -45,7 +47,9 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
+import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -77,12 +81,15 @@
private val testScope = kosmos.testScope
private val testHelper = kosmos.shortcutHelperTestHelper
private val sysUiState = kosmos.sysUiState
+ private val fakeUserTracker = kosmos.fakeUserTracker
+ private val mockRoleManager = kosmos.mockRoleManager
private val viewModel = kosmos.shortcutHelperViewModel
@Before
fun setUp() {
fakeSystemSource.setGroups(TestShortcuts.systemGroups)
fakeMultiTaskingSource.setGroups(TestShortcuts.multitaskingGroups)
+ fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
}
@Test
@@ -216,21 +223,21 @@
}
@Test
- fun shortcutsUiState_featureActive_emitsActiveWithFirstCategorySelectedByDefault() =
+ fun shortcutsUiState_noCurrentAppCategory_defaultSelectedCategoryIsSystem() =
testScope.runTest {
+ fakeCurrentAppsSource.setGroups(emptyList())
+
val uiState by collectLastValue(viewModel.shortcutsUiState)
testHelper.showFromActivity()
val activeUiState = uiState as ShortcutsUiState.Active
- assertThat(activeUiState.defaultSelectedCategory)
- .isEqualTo(activeUiState.shortcutCategories.first().type)
+ assertThat(activeUiState.defaultSelectedCategory).isEqualTo(System)
}
@Test
- fun shortcutsUiState_featureActive_emitsActiveWithCurrentAppsCategorySelectedWhenPresent() =
+ fun shortcutsUiState_currentAppCategoryPresent_currentAppIsDefaultSelected() =
testScope.runTest {
- fakeCurrentAppsSource.setGroups(TestShortcuts.currentAppGroups)
val uiState by collectLastValue(viewModel.shortcutsUiState)
testHelper.showFromActivity()
@@ -241,6 +248,24 @@
}
@Test
+ fun shortcutsUiState_currentAppIsLauncher_defaultSelectedCategoryIsSystem() =
+ testScope.runTest {
+ whenever(
+ mockRoleManager.getRoleHoldersAsUser(
+ RoleManager.ROLE_HOME,
+ fakeUserTracker.userHandle
+ )
+ )
+ .thenReturn(listOf(TestShortcuts.currentAppPackageName))
+ val uiState by collectLastValue(viewModel.shortcutsUiState)
+
+ testHelper.showFromActivity()
+
+ val activeUiState = uiState as ShortcutsUiState.Active
+ assertThat(activeUiState.defaultSelectedCategory).isEqualTo(System)
+ }
+
+ @Test
fun shortcutsUiState_userTypedQuery_filtersMatchingShortcutLabels() =
testScope.runTest {
fakeSystemSource.setGroups(
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 506c5ae..29cd9a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -30,6 +30,7 @@
import android.view.SurfaceControlViewHost
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
@@ -96,7 +97,8 @@
@Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
private lateinit var dockManager: DockManagerFake
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
@@ -199,6 +201,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
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 f726aae..e251ab5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.whenever
+import java.util.function.Predicate
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -46,7 +47,6 @@
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.clearInvocations
-import java.util.function.Predicate
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -54,70 +54,134 @@
class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
- @Mock
- private lateinit var windowManager: WindowManager
- @Mock
- private lateinit var keyguardViewMediator: KeyguardViewMediator
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var keyguardViewController: KeyguardViewController
- @Mock
- private lateinit var featureFlags: FeatureFlags
- @Mock
- private lateinit var biometricUnlockController: BiometricUnlockController
- @Mock
- private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
- @Mock
- private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock
- private lateinit var notificationShadeWindowController: NotificationShadeWindowController
- @Mock
- private lateinit var powerManager: PowerManager
- @Mock
- private lateinit var wallpaperManager: WallpaperManager
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var keyguardViewMediator: KeyguardViewMediator
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+ @Mock private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var powerManager: PowerManager
+ @Mock private lateinit var wallpaperManager: WallpaperManager
@Mock
private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub
private var surfaceControl1 = mock(SurfaceControl::class.java)
- private var remoteTarget1 = RemoteAnimationTarget(
- 0 /* taskId */, 0, surfaceControl1, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControl1, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var remoteTarget1 =
+ RemoteAnimationTarget(
+ 0 /* taskId */,
+ 0,
+ surfaceControl1,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControl1,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private var surfaceControl2 = mock(SurfaceControl::class.java)
- private var remoteTarget2 = RemoteAnimationTarget(
- 1 /* taskId */, 0, surfaceControl2, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControl2, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var remoteTarget2 =
+ RemoteAnimationTarget(
+ 1 /* taskId */,
+ 0,
+ surfaceControl2,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControl2,
+ Rect(),
+ mock(ActivityManager.RunningTaskInfo::class.java),
+ false
+ )
private lateinit var remoteAnimationTargets: Array<RemoteAnimationTarget>
private var surfaceControlWp = mock(SurfaceControl::class.java)
- private var wallpaperTarget = RemoteAnimationTarget(
- 2 /* taskId */, 0, surfaceControlWp, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
- mock(WindowConfiguration::class.java), false, surfaceControlWp, Rect(),
- mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private var wallpaperTarget =
+ RemoteAnimationTarget(
+ 2 /* taskId */,
+ 0,
+ surfaceControlWp,
+ false,
+ Rect(),
+ Rect(),
+ 0,
+ Point(),
+ Rect(),
+ Rect(),
+ mock(WindowConfiguration::class.java),
+ false,
+ surfaceControlWp,
+ Rect(),
+ 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 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>
+ private var shouldPerformSmartspaceTransition = false
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
- windowManager, context.resources,
- keyguardStateController, { keyguardViewMediator }, keyguardViewController,
- featureFlags, { biometricUnlockController }, statusBarStateController,
- notificationShadeWindowController, powerManager, wallpaperManager
- )
+ keyguardUnlockAnimationController =
+ object :
+ KeyguardUnlockAnimationController(
+ windowManager,
+ context.resources,
+ keyguardStateController,
+ { keyguardViewMediator },
+ keyguardViewController,
+ featureFlags,
+ { biometricUnlockController },
+ statusBarStateController,
+ notificationShadeWindowController,
+ powerManager,
+ wallpaperManager
+ ) {
+ override fun shouldPerformSmartspaceTransition(): Boolean =
+ shouldPerformSmartspaceTransition
+ }
keyguardUnlockAnimationController.setLauncherUnlockController(
- "", launcherUnlockAnimationController)
+ "",
+ launcherUnlockAnimationController
+ )
whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
whenever(powerManager.isInteractive).thenReturn(true)
@@ -159,8 +223,8 @@
)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1 })
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val params = captorSb.getLastValue()
@@ -171,15 +235,13 @@
// Also expect we've immediately asked the keyguard view mediator to finish the remote
// animation.
- verify(keyguardViewMediator, times(1)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(1))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
verifyNoMoreInteractions(surfaceTransactionApplier)
}
- /**
- * If we are not wake and unlocking, we expect the unlock animation to play normally.
- */
+ /** If we are not wake and unlocking, we expect the unlock animation to play normally. */
@Test
fun surfaceAnimation_ifNotWakeAndUnlocking() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(false)
@@ -193,18 +255,18 @@
)
// Since the animation is running, we should not have finished the remote animation.
- verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(0))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
@Test
fun onWakeAndUnlock_notifiesListenerWithTrue() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
- whenever(biometricUnlockController.mode).thenReturn(
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+ whenever(biometricUnlockController.mode)
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
- val listener = mock(
- KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ val listener =
+ mock(KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
@@ -221,11 +283,11 @@
@Test
fun onWakeAndUnlockFromDream_notifiesListenerWithFalse() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
- whenever(biometricUnlockController.mode).thenReturn(
- BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+ whenever(biometricUnlockController.mode)
+ .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
- val listener = mock(
- KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ val listener =
+ mock(KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
@@ -269,8 +331,8 @@
* keyguard. This means this was a swipe to dismiss gesture but the user flung the keyguard and
* lifted their finger while we were requesting the surface be made visible.
*
- * In this case, we should verify that we are playing the canned unlock animation and not
- * simply fading in the surface.
+ * In this case, we should verify that we are playing the canned unlock animation and not simply
+ * fading in the surface.
*/
@Test
fun playCannedUnlockAnimation_ifRequestedShowSurface_andFlinging() {
@@ -293,8 +355,8 @@
* ever happened and we're just playing the simple canned animation (happens via UDFPS unlock,
* long press on the lock icon, etc).
*
- * In this case, we should verify that we are playing the canned unlock animation and not
- * simply fading in the surface.
+ * In this case, we should verify that we are playing the canned unlock animation and not simply
+ * fading in the surface.
*/
@Test
fun playCannedUnlockAnimation_ifDidNotRequestShowSurface() {
@@ -332,11 +394,11 @@
keyguardUnlockAnimationController.willUnlockWithInWindowLauncherAnimations = true
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
assertTrue(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation())
@@ -353,11 +415,11 @@
var lastFadeOutAlpha = -1f
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- arrayOf(remoteTarget1, remoteTarget2),
- wallpaperTargets,
- lockWallpaperTargets,
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ arrayOf(remoteTarget1, remoteTarget2),
+ wallpaperTargets,
+ lockWallpaperTargets,
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
for (i in 0..10) {
@@ -367,19 +429,22 @@
keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(amount)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(2)).scheduleApply(
+ verify(surfaceTransactionApplier, times(2))
+ .scheduleApply(
captorSb.capture { sp ->
- sp.surface == surfaceControlWp || sp.surface == surfaceControlLockWp })
+ 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)
+ assertTrue(fadeInAlpha == 0f)
+ assertTrue(fadeOutAlpha == 1f)
} else if (amount == 1f) {
- assertTrue (fadeInAlpha == 1f)
- assertTrue (fadeOutAlpha == 0f)
+ assertTrue(fadeInAlpha == 1f)
+ assertTrue(fadeOutAlpha == 0f)
} else {
assertTrue(fadeInAlpha >= lastFadeInAlpha)
assertTrue(fadeOutAlpha <= lastFadeOutAlpha)
@@ -389,18 +454,16 @@
}
}
- /**
- * If we are not wake and unlocking, we expect the unlock animation to play normally.
- */
+ /** If we are not wake and unlocking, we expect the unlock animation to play normally. */
@Test
@DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceAnimation_multipleTargets() {
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- arrayOf(remoteTarget1, remoteTarget2),
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ arrayOf(remoteTarget1, remoteTarget2),
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Cancel the animator so we can verify only the setSurfaceBehind call below.
@@ -412,12 +475,18 @@
keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(0.5f)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(2)).scheduleApply(captorSb
- .capture { sp -> sp.surface == surfaceControl1 || sp.surface == surfaceControl2 })
+ verify(surfaceTransactionApplier, times(2))
+ .scheduleApply(
+ captorSb.capture { sp ->
+ sp.surface == surfaceControl1 || sp.surface == surfaceControl2
+ }
+ )
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1).description(
- "WallpaperSurface was expected to receive scheduleApply once"
- )).scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp})
+ verify(
+ surfaceTransactionApplier,
+ times(1).description("WallpaperSurface was expected to receive scheduleApply once")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val allParams = captorSb.getAllValues()
@@ -432,8 +501,8 @@
assertTrue(remainingTargets.isEmpty())
// Since the animation is running, we should not have finished the remote animation.
- verify(keyguardViewMediator, times(0)).exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
+ verify(keyguardViewMediator, times(0))
+ .exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */)
}
@Test
@@ -442,11 +511,11 @@
whenever(powerManager.isInteractive).thenReturn(false)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Cancel the animator so we can verify only the setSurfaceBehind call below.
@@ -457,12 +526,14 @@
keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1})
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " +
- "received scheduleApply")).scheduleApply(
- captorWp.capture { sp -> sp.surface == surfaceControlWp })
+ verify(
+ surfaceTransactionApplier,
+ atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val params = captorSb.getLastValue()
@@ -479,11 +550,11 @@
whenever(powerManager.isInteractive).thenReturn(true)
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
- remoteAnimationTargets,
- wallpaperTargets,
- arrayOf(),
- 0 /* startTime */,
- false /* requestedShowSurfaceBehindKeyguard */
+ remoteAnimationTargets,
+ wallpaperTargets,
+ arrayOf(),
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
)
// Stop the animator - we just want to test whether the override is not applied.
@@ -494,24 +565,31 @@
keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, times(1)).scheduleApply(
- captorSb.capture { sp -> sp.surface == surfaceControl1 })
+ verify(surfaceTransactionApplier, times(1))
+ .scheduleApply(captorSb.capture { sp -> sp.surface == surfaceControl1 })
val captorWp = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
- verify(surfaceTransactionApplier, atLeastOnce().description("Wallpaper surface has not " +
- "received scheduleApply")).scheduleApply(
- captorWp.capture { sp -> sp.surface == surfaceControlWp })
+ verify(
+ surfaceTransactionApplier,
+ atLeastOnce().description("Wallpaper surface has not " + "received scheduleApply")
+ )
+ .scheduleApply(captorWp.capture { sp -> sp.surface == surfaceControlWp })
val params = captorSb.getLastValue()
assertEquals(1f, params.alpha)
assertTrue(params.matrix.isIdentity)
- assertEquals("Wallpaper surface was expected to have opacity 1",
- 1f, captorWp.getLastValue().alpha)
+ assertEquals(
+ "Wallpaper surface was expected to have opacity 1",
+ 1f,
+ captorWp.getLastValue().alpha
+ )
verifyNoMoreInteractions(surfaceTransactionApplier)
}
@Test
- fun unlockToLauncherWithInWindowAnimations_ssViewIsVisible() {
+ fun unlockToLauncherWithInWindowAnimations_ssViewInVisible_whenPerformSSTransition() {
+ shouldPerformSmartspaceTransition = true
+
val mockLockscreenSmartspaceView = mock(View::class.java)
whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE)
keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
@@ -522,6 +600,19 @@
}
@Test
+ fun unlockToLauncherWithInWindowAnimations_ssViewVisible_whenNotPerformSSTransition() {
+ shouldPerformSmartspaceTransition = false
+
+ val mockLockscreenSmartspaceView = mock(View::class.java)
+ whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.VISIBLE)
+ keyguardUnlockAnimationController.lockscreenSmartspace = mockLockscreenSmartspaceView
+
+ keyguardUnlockAnimationController.unlockToLauncherWithInWindowAnimations()
+
+ verify(mockLockscreenSmartspaceView, never()).visibility = View.INVISIBLE
+ }
+
+ @Test
fun unlockToLauncherWithInWindowAnimations_ssViewIsInvisible() {
val mockLockscreenSmartspaceView = mock(View::class.java)
whenever(mockLockscreenSmartspaceView.visibility).thenReturn(View.INVISIBLE)
@@ -591,7 +682,7 @@
private var allArgs: MutableList<T> = mutableListOf()
fun capture(predicate: Predicate<T>): T {
- return argThat{x: T ->
+ return argThat { x: T ->
if (predicate.test(x)) {
allArgs.add(x)
return@argThat true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index e68a4a5..9de7528 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -70,10 +70,10 @@
import android.view.RemoteAnimationTarget;
import android.view.View;
import android.view.ViewRootImpl;
-import android.view.WindowManager;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
@@ -167,7 +167,7 @@
private @Mock BroadcastDispatcher mBroadcastDispatcher;
private @Mock DismissCallbackRegistry mDismissCallbackRegistry;
private @Mock DumpManager mDumpManager;
- private @Mock WindowManager mWindowManager;
+ private @Mock ViewCaptureAwareWindowManager mWindowManager;
private @Mock IActivityManager mActivityManager;
private @Mock ConfigurationController mConfigurationController;
private @Mock PowerManager mPowerManager;
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 7560a97..e3bdcd7 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
@@ -23,6 +23,7 @@
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.DialogTransitionAnimator
@@ -232,7 +233,8 @@
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
private lateinit var underTest: KeyguardQuickAffordanceInteractor
private lateinit var testScope: TestScope
@@ -327,6 +329,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
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 fd1bf54..591ce1a 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
@@ -23,6 +23,7 @@
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.DialogTransitionAnimator
@@ -232,7 +233,8 @@
@Mock private lateinit var expandable: Expandable
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
private lateinit var underTest: KeyguardQuickAffordanceInteractor
private lateinit var testScope: TestScope
@@ -327,6 +329,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
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 3b96be4..fc7f693 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
@@ -23,6 +23,7 @@
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
@@ -99,7 +100,8 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
@@ -237,6 +239,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
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 e89abf6..77977f3 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
@@ -23,6 +23,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
+import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
@@ -93,7 +94,8 @@
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
- @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
+ @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock
private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel
@@ -299,6 +301,7 @@
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
logger = logger,
+ metricsLogger = metricsLogger,
devicePolicyManager = devicePolicyManager,
dockManager = dockManager,
biometricSettingsRepository = biometricSettingsRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
index 7dd8028..f884b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -25,8 +25,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
@@ -34,10 +32,8 @@
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
import org.junit.After
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -46,7 +42,6 @@
private lateinit var dialog: AlertDialog
- private val flags = mock<FeatureFlagsClassic>()
private val appName = "Test App"
private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
@@ -54,11 +49,6 @@
private val resIdSingleAppDisabled =
R.string.media_projection_entry_app_permission_dialog_single_app_disabled
- @Before
- fun setUp() {
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
- }
-
@After
fun teardown() {
if (::dialog.isInitialized) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index 196bbb9..413aa55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -307,7 +307,7 @@
mNavigationBarController.mIsLargeScreen = false;
mNavigationBarController.mIsPhone = true;
- assertFalse(mNavigationBarController.supportsTaskbar());
+ assertTrue(mNavigationBarController.supportsTaskbar());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 31652a5..f90e1e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -47,6 +47,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
@@ -60,7 +61,8 @@
@RunWith(AndroidJUnit4::class)
@RunWithLooper
class FooterActionsViewModelTest : SysuiTestCase() {
- private val testScope = TestScope()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
private lateinit var utils: FooterActionsTestUtils
private val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
@@ -127,7 +129,7 @@
fun userSwitcher() = runTest {
val picture: Drawable = mock()
val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
- val settings = FakeGlobalSettings()
+ val settings = FakeGlobalSettings(testDispatcher)
val userId = 42
val userSwitcherControllerWrapper =
MockUserSwitcherControllerWrapper(currentUserName = "foo")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 988769f..90ffaf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -25,6 +25,7 @@
import android.view.ContextThemeWrapper
import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.Button
import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -381,6 +382,47 @@
}
@Test
+ fun testNonSwitchA11yClass_longClickActionHasCorrectLabel() {
+ val state =
+ QSTile.State().apply {
+ expandedAccessibilityClassName = Button::class.java.name
+ handlesLongClick = true
+ }
+ tileView.changeState(state)
+ val info = AccessibilityNodeInfo(tileView)
+ tileView.onInitializeAccessibilityNodeInfo(info)
+
+ assertThat(
+ info.actionList
+ .find {
+ it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id
+ }
+ ?.label
+ )
+ .isEqualTo(context.getString(R.string.accessibility_long_click_tile))
+ }
+
+ @Test
+ fun testNonSwitchA11yClass_disabledByPolicy_noLongClickAction() {
+ val state =
+ QSTile.State().apply {
+ expandedAccessibilityClassName = Button::class.java.name
+ handlesLongClick = true
+ disabledByPolicy = true
+ }
+ tileView.changeState(state)
+ val info = AccessibilityNodeInfo(tileView)
+ tileView.onInitializeAccessibilityNodeInfo(info)
+
+ assertThat(
+ info.actionList.find {
+ it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id
+ }
+ )
+ .isNull()
+ }
+
+ @Test
fun onStateChange_longPressEffectActive_withInvalidDuration_doesNotInitializeEffect() {
val state = QSTile.State() // A state that handles longPress
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index 74d9692..a5de7cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -47,8 +47,8 @@
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.After
@@ -82,9 +82,12 @@
@Mock private lateinit var dialogDelegate: ModesDialogDelegate
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
private val inputHandler = FakeQSTileIntentUserInputHandler()
private val zenModeRepository = FakeZenModeRepository()
- private val tileDataInteractor = ModesTileDataInteractor(zenModeRepository)
+ private val tileDataInteractor = ModesTileDataInteractor(zenModeRepository, testDispatcher)
private val mapper =
ModesTileMapper(
context.orCreateTestableResources
@@ -96,9 +99,6 @@
context.theme,
)
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
-
private lateinit var userActionInteractor: ModesTileUserActionInteractor
private lateinit var secureSettings: SecureSettings
private lateinit var testableLooper: TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index ce1a885..8d84c3e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -177,7 +177,6 @@
.thenReturn(false)
whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
.thenReturn(false)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
whenever(state.hasUserApprovedScreenRecording).thenReturn(false)
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
@@ -200,7 +199,6 @@
.thenReturn(false)
whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
.thenReturn(false)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false)
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 477c50b..6b16e78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -39,10 +39,8 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -52,12 +50,9 @@
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
-import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.DialogDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -110,7 +105,6 @@
private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
- private TestSystemUIDialogFactory mDialogFactory;
private static final int USER_ID = 10;
@@ -120,14 +114,6 @@
Context spiedContext = spy(mContext);
when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
- mDialogFactory = new TestSystemUIDialogFactory(
- mContext,
- Dependency.get(SystemUIDialogManager.class),
- Dependency.get(SysUiState.class),
- Dependency.get(BroadcastDispatcher.class),
- Dependency.get(DialogTransitionAnimator.class)
- );
-
mFeatureFlags = new FakeFeatureFlags();
when(mScreenCaptureDisabledDialogDelegate.createSysUIDialog())
.thenReturn(mScreenCaptureDisabledDialog);
@@ -251,7 +237,6 @@
@Test
public void testPoliciesFlagDisabled_screenCapturingNotAllowed_returnsNullDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -269,19 +254,7 @@
}
@Test
- public void testPartialScreenSharingDisabled_returnsLegacyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, false);
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, false);
-
- Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
- mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
-
- assertThat(dialog).isEqualTo(mScreenRecordSystemUIDialog);
- }
-
- @Test
public void testPoliciesFlagEnabled_screenCapturingNotAllowed_returnsDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(true);
@@ -293,7 +266,6 @@
@Test
public void testPoliciesFlagEnabled_screenCapturingAllowed_returnsNullDevicePolicyDialog() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -312,7 +284,6 @@
@Test
public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
- mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -324,32 +295,4 @@
/* hostUid= */ myUid(),
SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
}
-
- private static class TestSystemUIDialogFactory extends SystemUIDialog.Factory {
-
- @Nullable private DialogDelegate<SystemUIDialog> mLastDelegate;
- @Nullable private SystemUIDialog mLastCreatedDialog;
-
- TestSystemUIDialogFactory(
- Context context,
- SystemUIDialogManager systemUIDialogManager,
- SysUiState sysUiState,
- BroadcastDispatcher broadcastDispatcher,
- DialogTransitionAnimator dialogTransitionAnimator) {
- super(
- context,
- systemUIDialogManager,
- sysUiState,
- broadcastDispatcher,
- dialogTransitionAnimator);
- }
-
- @Override
- public SystemUIDialog create(SystemUIDialog.Delegate delegate) {
- SystemUIDialog dialog = super.create(delegate);
- mLastDelegate = delegate;
- mLastCreatedDialog = dialog;
- return dialog;
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index cc8d7d5..11b0bdf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -28,7 +28,6 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
@@ -51,7 +50,6 @@
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@@ -72,8 +70,6 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
-
val systemUIDialogFactory =
SystemUIDialog.Factory(
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
index 24e8b18..5e07aef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
@@ -228,7 +228,7 @@
)
val quickSharePendingIntent =
quickShareAction.actionIntent.intent.extras!!.getParcelable(
- ScreenshotController.EXTRA_ACTION_INTENT,
+ SmartActionsReceiver.EXTRA_ACTION_INTENT,
PendingIntent::class.java
)
@@ -266,7 +266,7 @@
assertEquals(
immutablePendingIntent,
quickShareAction.actionIntent.intent.extras!!.getParcelable(
- ScreenshotController.EXTRA_ACTION_INTENT,
+ SmartActionsReceiver.EXTRA_ACTION_INTENT,
PendingIntent::class.java
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
index 471fdc0..9dc5cfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
@@ -16,8 +16,8 @@
package com.android.systemui.screenshot;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE;
-import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
+import static com.android.systemui.screenshot.SmartActionsReceiver.EXTRA_ACTION_TYPE;
+import static com.android.systemui.screenshot.SmartActionsReceiver.EXTRA_ID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -57,7 +57,7 @@
MockitoAnnotations.initMocks(this);
mSmartActionsReceiver = new SmartActionsReceiver(mMockScreenshotSmartActions);
mIntent = new Intent(mContext, SmartActionsReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, mMockPendingIntent);
+ .putExtra(SmartActionsReceiver.EXTRA_ACTION_INTENT, mMockPendingIntent);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 8d3a29a..a295981 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -42,10 +42,10 @@
@SmallTest
class TakeScreenshotExecutorTest : SysuiTestCase() {
- private val controller = mock<ScreenshotController>()
+ private val controller = mock<LegacyScreenshotController>()
private val notificationsController0 = mock<ScreenshotNotificationsController>()
private val notificationsController1 = mock<ScreenshotNotificationsController>()
- private val controllerFactory = mock<ScreenshotController.Factory>()
+ private val controllerFactory = mock<InteractiveScreenshotHandler.Factory>()
private val callback = mock<TakeScreenshotService.RequestCallback>()
private val notificationControllerFactory = mock<ScreenshotNotificationsController.Factory>()
@@ -287,7 +287,7 @@
fun onCloseSystemDialogsReceived_controllerHasPendingTransitions() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
- whenever(controller.isPendingSharedTransition).thenReturn(true)
+ whenever(controller.isPendingSharedTransition()).thenReturn(true)
val onSaved = { _: Uri? -> }
screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index 9986205..a8d5008 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.screenshot.appclips;
import static android.app.Activity.RESULT_OK;
+import static android.app.ActivityManager.RunningTaskInfo;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
@@ -32,7 +33,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IActivityTaskManager;
import android.app.assist.AssistContent;
import android.content.ComponentName;
@@ -103,7 +103,7 @@
private static final String BACKLINKS_TASK_APP_NAME = "Backlinks app";
private static final String BACKLINKS_TASK_PACKAGE_NAME = "backlinksTaskPackageName";
- private static final RootTaskInfo TASK_THAT_SUPPORTS_BACKLINKS =
+ private static final RunningTaskInfo TASK_THAT_SUPPORTS_BACKLINKS =
createTaskInfoForBacklinksTask();
private static final AssistContent ASSIST_CONTENT_FOR_BACKLINKS_TASK =
createAssistContentForBacklinksTask();
@@ -233,6 +233,10 @@
assertThat(backlinksData.getText().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
assertThat(backlinksData.getCompoundDrawablesRelative()[0]).isEqualTo(FAKE_DRAWABLE);
+ // Verify dropdown icon is not shown and there are no click listeners on text view.
+ assertThat(backlinksData.getCompoundDrawablesRelative()[2]).isNull();
+ assertThat(backlinksData.hasOnClickListeners()).isFalse();
+
CheckBox backlinksIncludeData = mActivity.findViewById(R.id.backlinks_include_data);
assertThat(backlinksIncludeData.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(backlinksIncludeData.getText().toString())
@@ -258,20 +262,71 @@
assertThat(backlinksData.getVisibility()).isEqualTo(View.GONE);
}
+ @Test
+ @EnableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS)
+ public void appClipsLaunched_backlinks_multipleBacklinksAvailable_defaultShown()
+ throws RemoteException {
+ // Set up mocking for multiple backlinks.
+ ResolveInfo resolveInfo1 = createBacklinksTaskResolveInfo();
+
+ int taskId2 = BACKLINKS_TASK_ID + 2;
+ String package2 = BACKLINKS_TASK_PACKAGE_NAME + 2;
+ String appName2 = BACKLINKS_TASK_APP_NAME + 2;
+
+ ResolveInfo resolveInfo2 = createBacklinksTaskResolveInfo();
+ ActivityInfo activityInfo2 = resolveInfo2.activityInfo;
+ activityInfo2.name = appName2;
+ activityInfo2.packageName = package2;
+ activityInfo2.applicationInfo.packageName = package2;
+ RunningTaskInfo runningTaskInfo2 = createTaskInfoForBacklinksTask();
+ runningTaskInfo2.taskId = taskId2;
+ runningTaskInfo2.topActivity = new ComponentName(package2, "backlinksClass");
+ runningTaskInfo2.topActivityInfo = resolveInfo2.activityInfo;
+ runningTaskInfo2.baseIntent = new Intent().setComponent(runningTaskInfo2.topActivity);
+
+ when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
+ mDisplayIdCaptor.capture()))
+ .thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS, runningTaskInfo2));
+ when(mPackageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(resolveInfo1,
+ resolveInfo1, resolveInfo1, resolveInfo2, resolveInfo2, resolveInfo2);
+ when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
+
+ // Using same AssistContent data for both tasks.
+ mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, BACKLINKS_TASK_ID);
+ mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, taskId2);
+
+ // Mocking complete, trigger backlinks.
+ launchActivity();
+ waitForIdleSync();
+
+ // Verify default backlink shown to user and text view has on click listener.
+ TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
+ assertThat(backlinksData.getText().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
+ assertThat(backlinksData.hasOnClickListeners()).isTrue();
+
+ // Verify dropdown icon is not null.
+ assertThat(backlinksData.getCompoundDrawablesRelative()[2]).isNotNull();
+ }
+
private void setUpMocksForBacklinks() throws RemoteException {
- when(mAtmService.getAllRootTaskInfosOnDisplay(mDisplayIdCaptor.capture()))
+ when(mAtmService.getTasks(eq(Integer.MAX_VALUE), eq(false), eq(false),
+ mDisplayIdCaptor.capture()))
.thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS));
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(ASSIST_CONTENT_FOR_BACKLINKS_TASK);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+ mockForAssistContent(ASSIST_CONTENT_FOR_BACKLINKS_TASK, BACKLINKS_TASK_ID);
when(mPackageManager
.resolveActivity(any(Intent.class), anyInt()))
.thenReturn(createBacklinksTaskResolveInfo());
when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
}
+ private void mockForAssistContent(AssistContent expected, int taskId) {
+ doAnswer(invocation -> {
+ AssistContentRequester.Callback callback = invocation.getArgument(1);
+ callback.onAssistContentAvailable(expected);
+ return null;
+ }).when(mAssistContentRequester).requestAssistContent(eq(taskId), any());
+ }
+
private void launchActivity() {
launchActivity(createResultReceiver(FAKE_CONSUMER));
}
@@ -319,8 +374,8 @@
return resolveInfo;
}
- private static RootTaskInfo createTaskInfoForBacklinksTask() {
- RootTaskInfo taskInfo = new RootTaskInfo();
+ private static RunningTaskInfo createTaskInfoForBacklinksTask() {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = BACKLINKS_TASK_ID;
taskInfo.isVisible = true;
taskInfo.isRunning = true;
@@ -328,7 +383,6 @@
taskInfo.topActivity = new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, "backlinksClass");
taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo;
taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity);
- taskInfo.childTaskIds = new int[]{BACKLINKS_TASK_ID + 1};
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
return taskInfo;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
index 193d29c..178547e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -37,7 +37,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityTaskManager.RootTaskInfo;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.IActivityTaskManager;
import android.app.assist.AssistContent;
import android.content.ClipData;
@@ -107,7 +107,7 @@
mPackageManagerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
// Set up mocking for backlinks.
- when(mAtmService.getAllRootTaskInfosOnDisplay(DEFAULT_DISPLAY))
+ when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY))
.thenReturn(List.of(createTaskInfoForBacklinksTask()));
when(mPackageManager.resolveActivity(mPackageManagerIntentCaptor.capture(), anyInt()))
.thenReturn(createBacklinksTaskResolveInfo());
@@ -190,11 +190,7 @@
Uri expectedUri = Uri.parse("https://developers.android.com");
AssistContent contentWithUri = new AssistContent();
contentWithUri.setWebUri(expectedUri);
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(contentWithUri);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+ mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID);
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
@@ -203,7 +199,7 @@
assertThat(queriedIntent.getData()).isEqualTo(expectedUri);
assertThat(queriedIntent.getAction()).isEqualTo(ACTION_VIEW);
- InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue();
+ InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue();
assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
ClipData clipData = result.getClipData();
ClipDescription resultDescription = clipData.getDescription();
@@ -211,6 +207,8 @@
assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_URILIST);
assertThat(clipData.getItemCount()).isEqualTo(1);
assertThat(clipData.getItemAt(0).getUri()).isEqualTo(expectedUri);
+
+ assertThat(result).isEqualTo(mViewModel.getBacklinksLiveData().getValue().get(0));
}
@Test
@@ -218,12 +216,8 @@
Uri expectedUri = Uri.parse("https://developers.android.com");
AssistContent contentWithUri = new AssistContent();
contentWithUri.setWebUri(expectedUri);
+ mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID);
resetPackageManagerMockingForUsingFallbackBacklinks();
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(contentWithUri);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
@@ -236,11 +230,7 @@
Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME);
AssistContent contentWithAppProvidedIntent = new AssistContent();
contentWithAppProvidedIntent.setIntent(expectedIntent);
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(contentWithAppProvidedIntent);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+ mockForAssistContent(contentWithAppProvidedIntent, BACKLINKS_TASK_ID);
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
@@ -248,7 +238,7 @@
Intent queriedIntent = mPackageManagerIntentCaptor.getValue();
assertThat(queriedIntent.getPackage()).isEqualTo(expectedIntent.getPackage());
- InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue();
+ InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue();
assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
ClipData clipData = result.getClipData();
ClipDescription resultDescription = clipData.getDescription();
@@ -263,12 +253,8 @@
Intent expectedIntent = new Intent().setPackage(BACKLINKS_TASK_PACKAGE_NAME);
AssistContent contentWithAppProvidedIntent = new AssistContent();
contentWithAppProvidedIntent.setIntent(expectedIntent);
+ mockForAssistContent(contentWithAppProvidedIntent, BACKLINKS_TASK_ID);
resetPackageManagerMockingForUsingFallbackBacklinks();
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(contentWithAppProvidedIntent);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
@@ -278,11 +264,7 @@
@Test
public void triggerBacklinks_shouldUpdateBacklinks_withMainLauncherIntent() {
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(EMPTY_ASSIST_CONTENT);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+ mockForAssistContent(EMPTY_ASSIST_CONTENT, BACKLINKS_TASK_ID);
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
@@ -298,15 +280,12 @@
@Test
public void triggerBacklinks_withNonResolvableMainLauncherIntent_noBacklinksAvailable() {
reset(mPackageManager);
- doAnswer(invocation -> {
- AssistContentRequester.Callback callback = invocation.getArgument(1);
- callback.onAssistContentAvailable(EMPTY_ASSIST_CONTENT);
- return null;
- }).when(mAssistContentRequester).requestAssistContent(eq(BACKLINKS_TASK_ID), any());
+ mockForAssistContent(EMPTY_ASSIST_CONTENT, BACKLINKS_TASK_ID);
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
+ assertThat(mViewModel.mSelectedBacklinksLiveData.getValue()).isNull();
assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull();
}
@@ -314,14 +293,15 @@
public void triggerBacklinks_nonStandardActivityIgnored_noBacklinkAvailable()
throws RemoteException {
reset(mAtmService);
- RootTaskInfo taskInfo = createTaskInfoForBacklinksTask();
+ RunningTaskInfo taskInfo = createTaskInfoForBacklinksTask();
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- when(mAtmService.getAllRootTaskInfosOnDisplay(DEFAULT_DISPLAY))
+ when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY))
.thenReturn(List.of(taskInfo));
mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
waitForIdleSync();
+ assertThat(mViewModel.mSelectedBacklinksLiveData.getValue()).isNull();
assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull();
}
@@ -330,9 +310,68 @@
mViewModel.triggerBacklinks(Set.of(BACKLINKS_TASK_ID), DEFAULT_DISPLAY);
waitForIdleSync();
+ assertThat(mViewModel.mSelectedBacklinksLiveData.getValue()).isNull();
assertThat(mViewModel.getBacklinksLiveData().getValue()).isNull();
}
+ @Test
+ public void triggerBacklinks_multipleAppsOnScreen_multipleBacklinksAvailable()
+ throws RemoteException {
+ // Set up mocking for multiple backlinks.
+ reset(mAtmService, mPackageManager);
+ RunningTaskInfo runningTaskInfo1 = createTaskInfoForBacklinksTask();
+ ResolveInfo resolveInfo1 = createBacklinksTaskResolveInfo();
+
+ int taskId2 = BACKLINKS_TASK_ID + 2;
+ String package2 = BACKLINKS_TASK_PACKAGE_NAME + 2;
+ String appName2 = BACKLINKS_TASK_APP_NAME + 2;
+
+ ResolveInfo resolveInfo2 = createBacklinksTaskResolveInfo();
+ ActivityInfo activityInfo2 = resolveInfo2.activityInfo;
+ activityInfo2.name = appName2;
+ activityInfo2.packageName = package2;
+ activityInfo2.applicationInfo.packageName = package2;
+ RunningTaskInfo runningTaskInfo2 = createTaskInfoForBacklinksTask();
+ runningTaskInfo2.taskId = taskId2;
+ runningTaskInfo2.topActivity = new ComponentName(package2, "backlinksClass");
+ runningTaskInfo2.topActivityInfo = resolveInfo2.activityInfo;
+ runningTaskInfo2.baseIntent = new Intent().setComponent(runningTaskInfo2.topActivity);
+
+ // For each task, the logic queries PM 3 times, twice for verifying if an app can be
+ // launched via launcher and once with the data provided in backlink intent.
+ when(mPackageManager.resolveActivity(any(), anyInt())).thenReturn(resolveInfo1,
+ resolveInfo1, resolveInfo1, resolveInfo2, resolveInfo2, resolveInfo2);
+ when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
+ when(mAtmService.getTasks(Integer.MAX_VALUE, false, false, DEFAULT_DISPLAY))
+ .thenReturn(List.of(runningTaskInfo1, runningTaskInfo2));
+
+ // Using app provided web uri for the first backlink.
+ Uri expectedUri = Uri.parse("https://developers.android.com");
+ AssistContent contentWithUri = new AssistContent();
+ contentWithUri.setWebUri(expectedUri);
+ mockForAssistContent(contentWithUri, BACKLINKS_TASK_ID);
+
+ // Using app provided intent for the second backlink.
+ Intent expectedIntent = new Intent().setPackage(package2);
+ AssistContent contentWithAppProvidedIntent = new AssistContent();
+ contentWithAppProvidedIntent.setIntent(expectedIntent);
+ mockForAssistContent(contentWithAppProvidedIntent, taskId2);
+
+ // Set up complete, trigger the backlinks action.
+ mViewModel.triggerBacklinks(Collections.emptySet(), DEFAULT_DISPLAY);
+ waitForIdleSync();
+
+ // Verify two backlinks are received and the first backlink is set as default selected.
+ assertThat(mViewModel.mSelectedBacklinksLiveData.getValue().getClipData().getItemAt(
+ 0).getUri()).isEqualTo(expectedUri);
+ List<InternalBacklinksData> actualBacklinks = mViewModel.getBacklinksLiveData().getValue();
+ assertThat(actualBacklinks).hasSize(2);
+ assertThat(actualBacklinks.get(0).getClipData().getItemAt(0).getUri())
+ .isEqualTo(expectedUri);
+ assertThat(actualBacklinks.get(1).getClipData().getItemAt(0).getIntent())
+ .isEqualTo(expectedIntent);
+ }
+
private void resetPackageManagerMockingForUsingFallbackBacklinks() {
ResolveInfo backlinksTaskResolveInfo = createBacklinksTaskResolveInfo();
reset(mPackageManager);
@@ -350,7 +389,7 @@
}
private void verifyMainLauncherBacklinksIntent() {
- InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue();
+ InternalBacklinksData result = mViewModel.mSelectedBacklinksLiveData.getValue();
assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
ClipData clipData = result.getClipData();
@@ -368,6 +407,14 @@
new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, BACKLINKS_TASK_APP_NAME));
}
+ private void mockForAssistContent(AssistContent expected, int taskId) {
+ doAnswer(invocation -> {
+ AssistContentRequester.Callback callback = invocation.getArgument(1);
+ callback.onAssistContentAvailable(expected);
+ return null;
+ }).when(mAssistContentRequester).requestAssistContent(eq(taskId), any());
+ }
+
private static ResolveInfo createBacklinksTaskResolveInfo() {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.applicationInfo = new ApplicationInfo();
@@ -379,8 +426,8 @@
return resolveInfo;
}
- private static RootTaskInfo createTaskInfoForBacklinksTask() {
- RootTaskInfo taskInfo = new RootTaskInfo();
+ private static RunningTaskInfo createTaskInfoForBacklinksTask() {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = BACKLINKS_TASK_ID;
taskInfo.isVisible = true;
taskInfo.isRunning = true;
@@ -388,7 +435,6 @@
taskInfo.topActivity = new ComponentName(BACKLINKS_TASK_PACKAGE_NAME, "backlinksClass");
taskInfo.topActivityInfo = createBacklinksTaskResolveInfo().activityInfo;
taskInfo.baseIntent = new Intent().setComponent(taskInfo.topActivity);
- taskInfo.childTaskIds = new int[]{BACKLINKS_TASK_ID + 1};
taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
return taskInfo;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 967df39..5de31d88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -429,6 +429,7 @@
fun gestureExclusionZone_setAfterInit() =
with(kosmos) {
testScope.runTest {
+ whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR)
goToScene(CommunalScenes.Communal)
assertThat(containerView.systemGestureExclusionRects)
@@ -450,10 +451,37 @@
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
+ fun gestureExclusionZone_setAfterInit_rtl() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL)
+ goToScene(CommunalScenes.Communal)
+
+ assertThat(containerView.systemGestureExclusionRects)
+ .containsExactly(
+ Rect(
+ /* left= */ 0,
+ /* top= */ TOP_SWIPE_REGION_WIDTH,
+ /* right= */ CONTAINER_WIDTH,
+ /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
+ ),
+ Rect(
+ /* left= */ 0,
+ /* top= */ 0,
+ /* right= */ CONTAINER_WIDTH,
+ /* bottom= */ CONTAINER_HEIGHT
+ )
+ )
+ }
+ }
+
+ @Test
@EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
fun gestureExclusionZone_setAfterInit_backGestureEnabled() =
with(kosmos) {
testScope.runTest {
+ whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_LTR)
goToScene(CommunalScenes.Communal)
assertThat(containerView.systemGestureExclusionRects)
@@ -475,6 +503,32 @@
}
@Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
+ fun gestureExclusionZone_setAfterInit_backGestureEnabled_rtl() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(containerView.layoutDirection).thenReturn(View.LAYOUT_DIRECTION_RTL)
+ goToScene(CommunalScenes.Communal)
+
+ assertThat(containerView.systemGestureExclusionRects)
+ .containsExactly(
+ Rect(
+ /* left= */ FAKE_INSETS.left,
+ /* top= */ TOP_SWIPE_REGION_WIDTH,
+ /* right= */ CONTAINER_WIDTH - FAKE_INSETS.right,
+ /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
+ ),
+ Rect(
+ /* left= */ FAKE_INSETS.left,
+ /* top= */ 0,
+ /* right= */ CONTAINER_WIDTH,
+ /* bottom= */ CONTAINER_HEIGHT
+ )
+ )
+ }
+ }
+
+ @Test
fun gestureExclusionZone_unsetWhenShadeOpen() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 06a883c..b7ce336 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -44,7 +44,6 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -190,7 +189,6 @@
mKosmos.getKeyguardTransitionInteractor();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
mKeyguardRepository,
- new FakeCommandQueue(),
powerInteractor,
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(configurationRepository),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
deleted file mode 100644
index 3908529..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.statusbar.notification.domain.interactor
-
-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.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-class SeenNotificationsInteractorTest : SysuiTestCase() {
-
- private val repository = ActiveNotificationListRepository()
- private val underTest = SeenNotificationsInteractor(repository)
-
- @Test
- fun testNoFilteredOutSeenNotifications() = runTest {
- val hasFilteredOutSeenNotifications by
- collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
- underTest.setHasFilteredOutSeenNotifications(false)
-
- assertThat(hasFilteredOutSeenNotifications).isFalse()
- }
-
- @Test
- fun testHasFilteredOutSeenNotifications() = runTest {
- val hasFilteredOutSeenNotifications by
- collectLastValue(underTest.hasFilteredOutSeenNotifications)
-
- underTest.setHasFilteredOutSeenNotifications(true)
-
- assertThat(hasFilteredOutSeenNotifications).isTrue()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 1060b62..c36a046 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -91,7 +91,6 @@
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -188,12 +187,8 @@
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
-
- private final ActiveNotificationListRepository mActiveNotificationsRepository =
- new ActiveNotificationListRepository();
-
private final SeenNotificationsInteractor mSeenNotificationsInteractor =
- new SeenNotificationsInteractor(mActiveNotificationsRepository);
+ mKosmos.getSeenNotificationsInteractor();
private NotificationStackScrollLayoutController mController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index e46906f..4762527 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -66,7 +66,8 @@
repository = powerRepository,
falsingCollector = mock(),
screenOffAnimationController = mock(),
- statusBarStateController = mock()
+ statusBarStateController = mock(),
+ cameraGestureHelper = mock(),
)
private val configurationRepository =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 5e5586d..d9e9495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.assist.AssistManager;
import com.android.systemui.emergency.EmergencyGestureModule.EmergencyGestureIntentFactory;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
import com.android.systemui.recents.ScreenPinningRequest;
@@ -103,6 +104,7 @@
@Mock private QSHost mQSHost;
@Mock private ActivityStarter mActivityStarter;
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
+ @Mock private KeyguardInteractor mKeyguardInteractor;
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -140,6 +142,7 @@
mUserTracker,
mQSHost,
mActivityStarter,
+ mKeyguardInteractor,
mEmergencyGestureIntentFactory);
when(mUserTracker.getUserHandle()).thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index fd2dead..e670884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -334,6 +334,7 @@
/* typeVisibilityMap = */ booleanArrayOf(),
/* isRound = */ false,
/* forceConsumingTypes = */ 0,
+ /* forceConsumingCaptionBar = */ false,
/* suppressScrimTypes = */ 0,
/* displayCutout = */ DisplayCutout.NO_CUTOUT,
/* roundedCorners = */ RoundedCorners.NO_ROUNDED_CORNERS,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 31f93b4..af5e60e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -94,6 +95,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -166,6 +168,7 @@
@Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private DeviceEntryInteractor mDeviceEntryInteractor;
+ @Mock private SceneInteractor mSceneInteractor;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback
@@ -233,7 +236,7 @@
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class),
+ () -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
() -> mDeviceEntryInteractor) {
@Override
@@ -270,21 +273,23 @@
}
@Test
- public void showBouncer_onlyWhenShowing() {
+ public void showPrimaryBouncer_onlyWhenShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
+ verify(mSceneInteractor, never()).changeScene(any(), any());
}
@Test
- public void showBouncer_notWhenBouncerAlreadyShowing() {
+ public void showPrimaryBouncer_notWhenBouncerAlreadyShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.Password);
mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
+ verify(mSceneInteractor, never()).changeScene(any(), any());
}
@Test
@@ -753,7 +758,7 @@
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
mock(JavaAdapter.class),
- () -> mock(SceneInteractor.class),
+ () -> mSceneInteractor,
mock(StatusBarKeyguardViewManagerInteractor.class),
() -> mDeviceEntryInteractor) {
@Override
@@ -1104,9 +1109,9 @@
@Test
@EnableSceneContainer
- public void showPrimaryBouncer_attemptDeviceEntry() {
+ public void showPrimaryBouncer() {
mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
- verify(mDeviceEntryInteractor).attemptDeviceEntry();
+ verify(mSceneInteractor).changeScene(eq(Scenes.Bouncer), anyString());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index 8f49ba3..c4371fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -38,10 +38,8 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
-import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
-import com.android.systemui.statusbar.notification.icon.IconPack
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -256,139 +254,6 @@
.isGreaterThan(0)
}
- @Test
- @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun onEntryUpdated_notifIconsNotSet_noIconInChip() {
- val notification = createOngoingCallNotifEntry()
-
- notifCollectionListener.onEntryUpdated(notification)
-
- assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon)).isNull()
- }
-
- @Test
- @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun onEntryUpdated_notifIconsSetToNull_noIconInChip() {
- val notification = createOngoingCallNotifEntry()
- notification.icons = IconPack.buildEmptyPack(/* fromSource= */ null)
-
- notifCollectionListener.onEntryUpdated(notification)
-
- assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon)).isNull()
- }
-
- @Test
- @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun onEntryUpdated_notifIconsIncluded_statusBarChipIconUsed() {
- val notification = createOngoingCallNotifEntry()
-
- val statusBarChipIconView =
- StatusBarIconView(
- context,
- /* slot= */ "OngoingCallControllerTest",
- notification.sbn,
- )
- notification.icons =
- IconPack.buildPack(
- /* statusBarIcon= */ mock(StatusBarIconView::class.java),
- statusBarChipIconView,
- /* shelfIcon= */ mock(StatusBarIconView::class.java),
- /* aodIcon= */ mock(StatusBarIconView::class.java),
- /* source= */ null,
- )
-
- notifCollectionListener.onEntryUpdated(notification)
-
- assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon))
- .isEqualTo(statusBarChipIconView)
- assertThat(statusBarChipIconView.contentDescription)
- .isEqualTo(context.resources.getString(R.string.ongoing_phone_call_content_description))
- }
-
- @Test
- @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun onEntryUpdated_newNotifIcon_newIconUsed() {
- val notification = createOngoingCallNotifEntry()
-
- val firstStatusBarChipIconView =
- StatusBarIconView(
- context,
- /* slot= */ "OngoingCallControllerTest",
- notification.sbn,
- )
- notification.icons =
- IconPack.buildPack(
- /* statusBarIcon= */ mock(StatusBarIconView::class.java),
- firstStatusBarChipIconView,
- /* shelfIcon= */ mock(StatusBarIconView::class.java),
- /* aodIcon= */ mock(StatusBarIconView::class.java),
- /* source= */ null,
- )
- notifCollectionListener.onEntryUpdated(notification)
-
- assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon))
- .isEqualTo(firstStatusBarChipIconView)
-
- // WHEN the notification is updated with a new icon
- val secondStatusBarChipIconView =
- StatusBarIconView(
- context,
- /* slot= */ "OngoingCallControllerTestTheSecond",
- notification.sbn,
- )
- notification.icons =
- IconPack.buildPack(
- /* statusBarIcon= */ mock(StatusBarIconView::class.java),
- secondStatusBarChipIconView,
- /* shelfIcon= */ mock(StatusBarIconView::class.java),
- /* aodIcon= */ mock(StatusBarIconView::class.java),
- /* source= */ null,
- )
- notifCollectionListener.onEntryUpdated(notification)
-
- // THEN the new icon is used
- assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon))
- .isEqualTo(secondStatusBarChipIconView)
- }
-
- @Test
- @DisableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
- fun chipViewReinflated_iconViewMovedToNewChip() {
- val notification = createOngoingCallNotifEntry()
- val statusBarChipIconView =
- StatusBarIconView(
- context,
- /* slot= */ "OngoingCallControllerTest",
- notification.sbn,
- )
- notification.icons =
- IconPack.buildPack(
- /* statusBarIcon= */ mock(StatusBarIconView::class.java),
- statusBarChipIconView,
- /* shelfIcon= */ mock(StatusBarIconView::class.java),
- /* aodIcon= */ mock(StatusBarIconView::class.java),
- /* source= */ null,
- )
-
- notifCollectionListener.onEntryUpdated(notification)
-
- assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon))
- .isEqualTo(statusBarChipIconView)
-
- // WHEN we get a new chip view
- lateinit var newChipView: View
- TestableLooper.get(this).runWithLooper {
- newChipView =
- LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null)
- }
- controller.setChipView(newChipView)
-
- // THEN the icon is detached from the old view and attached to the new one
- assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_icon)).isNull()
- assertThat(newChipView.findViewById<View>(R.id.ongoing_activity_chip_icon))
- .isEqualTo(statusBarChipIconView)
- }
-
/** Regression test for b/194731244. */
@Test
fun onEntryUpdated_calledManyTimes_uidObserverOnlyRegisteredOnce() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
index 917e5b8..0641e17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt
@@ -62,7 +62,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- settings = FakeGlobalSettings()
+ settings = FakeGlobalSettings(testContext)
whenever(telephonyManager.emergencyCallbackMode).thenReturn(false)
whenever(subscriptionManager.activeSubscriptionIdList).thenReturn(intArrayOf())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
index 49aedccd..bebf1cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -36,7 +36,6 @@
import android.media.AudioManager;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
-import android.util.Pair;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -70,6 +69,8 @@
private CsdWarningDialog mDialog;
private static final String DISMISS_CSD_NOTIFICATION =
"com.android.systemui.volume.DISMISS_CSD_NOTIFICATION";
+ private final Optional<ImmutableList<CsdWarningAction>> mEmptyActions =
+ Optional.of(ImmutableList.of());
@Before
public void setup() {
@@ -87,7 +88,7 @@
// instantiate directly instead of via factory; we don't want executor to be @Background
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("", new Intent()))),
+ mEmptyActions,
mFakeBroadcastDispatcher);
mDialog.show();
@@ -104,7 +105,7 @@
FakeExecutor executor = new FakeExecutor(new FakeSystemClock());
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("", new Intent()))),
+ mEmptyActions,
mFakeBroadcastDispatcher);
mDialog.show();
@@ -121,7 +122,7 @@
.setPackage(mContext.getPackageName());
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("Undo", undoIntent))),
+ Optional.of(ImmutableList.of(new CsdWarningAction("Undo", undoIntent, false))),
mFakeBroadcastDispatcher);
when(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)).thenReturn(25);
@@ -148,7 +149,7 @@
.setPackage(mContext.getPackageName());
mDialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
mAudioManager, mNotificationManager, executor, null,
- Optional.of(ImmutableList.of(new Pair("Undo", undoIntent))),
+ Optional.of(ImmutableList.of(new CsdWarningAction("Undo", undoIntent, false))),
mFakeBroadcastDispatcher);
Intent dismissIntent = new Intent(DISMISS_CSD_NOTIFICATION)
.setPackage(mContext.getPackageName());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 3f5dc82..8b7d921 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -252,7 +252,6 @@
}
@Test
- @EnableFlags(Flags.FLAG_VOLUME_DIALOG_AUDIO_SHARING_FIX)
public void handleAudioSharingStreamVolumeChanges_updateState() {
ArgumentCaptor<VolumeDialogController.State> stateCaptor =
ArgumentCaptor.forClass(VolumeDialogController.State.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index b5cbf59..caa1779 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -44,7 +44,6 @@
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
-import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -57,7 +56,6 @@
import android.provider.Settings;
import android.testing.TestableLooper;
import android.util.Log;
-import android.util.Pair;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -166,7 +164,7 @@
new CsdWarningDialog.Factory() {
@Override
public CsdWarningDialog create(int warningType, Runnable onCleanup,
- Optional<ImmutableList<Pair<String, Intent>>> actionIntents) {
+ Optional<ImmutableList<CsdWarningAction>> actionIntents) {
return mCsdWarningDialog;
}
};
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 60b5b5d..d6b4d2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -98,6 +98,8 @@
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
+import com.android.app.viewcapture.ViewCapture;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
@@ -210,6 +212,7 @@
import java.util.Optional;
import java.util.concurrent.Executor;
+import kotlin.Lazy;
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
@@ -342,6 +345,8 @@
private Icon mAppBubbleIcon;
@Mock
private Display mDefaultDisplay;
+ @Mock
+ private Lazy<ViewCapture> mLazyViewCapture;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private ShadeInteractor mShadeInteractor;
@@ -407,7 +412,8 @@
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
new FakeWindowRootViewComponent.Factory(mNotificationShadeWindowView),
- mWindowManager,
+ new ViewCaptureAwareWindowManager(mWindowManager, mLazyViewCapture,
+ /* isViewCaptureEnabled= */ false),
mActivityManager,
mDozeParameters,
mStatusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/android/app/role/RoleManagerKosmos.kt
similarity index 67%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
rename to packages/SystemUI/tests/utils/src/android/app/role/RoleManagerKosmos.kt
index 37c9552..356bc86 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/app/role/RoleManagerKosmos.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package android.app.role
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
+import com.android.systemui.util.mockito.mock
-val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
+val Kosmos.mockRoleManager: RoleManager by Kosmos.Fixture { mock() }
+
+var Kosmos.roleManager: RoleManager by Kosmos.Fixture { mockRoleManager }
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 a124b34..27a2cab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -271,10 +271,14 @@
}
protected void waitForIdleSync() {
- if (mHandler == null) {
- mHandler = new Handler(Looper.getMainLooper());
+ if (isRobolectricTest()) {
+ mRealInstrumentation.waitForIdleSync();
+ } else {
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+ waitForIdleSync(mHandler);
}
- waitForIdleSync(mHandler);
}
protected void waitForUiOffloadThread() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 0b6b816..5063140 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -33,6 +33,7 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.biometrics.AuthController
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.camera.CameraGestureHelper
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -143,6 +144,7 @@
@get:Provides val primaryBouncerInteractor: PrimaryBouncerInteractor = mock(),
@get:Provides val keyguardStateController: KeyguardStateController = mock(),
@get:Provides val globalSettings: GlobalSettings = mock(),
+ @get:Provides val cameraGestureHelper: CameraGestureHelper = mock(),
// log buffers
@get:[Provides BroadcastDispatcherLog]
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
index 37c9552..1f04a44 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activatable/ActivatableExt.kt
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.activatable
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
-val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
+/** Activates [activatable] for the duration of the test. */
+fun Activatable.activateIn(testScope: TestScope) {
+ testScope.backgroundScope.launch { activate() }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/CameraGestureHelperKosmos.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/camera/CameraGestureHelperKosmos.kt
index 37c9552..931567e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/CameraGestureHelperKosmos.kt
@@ -14,11 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.camera
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
-import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import org.mockito.Mockito.mock
-val Kosmos.partitionedGridLayout by
- Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
+val Kosmos.cameraGestureHelper: CameraGestureHelper by
+ Kosmos.Fixture<CameraGestureHelper> {
+ mock(CameraGestureHelper::class.java).also { helper ->
+ whenever(helper.canCameraGestureBeLaunched(any())).thenReturn(true)
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index 28355e1..ca748b66 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -16,10 +16,16 @@
package com.android.systemui.haptics.qs
-import com.android.systemui.classifier.falsingManager
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.policy.keyguardStateController
val Kosmos.qsLongPressEffect by
- Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardStateController, falsingManager) }
+ Kosmos.Fixture {
+ QSLongPressEffect(
+ vibratorHelper,
+ keyguardStateController,
+ FakeLogBuffer.Factory.create(),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
index 8e4461d..444baa0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/inputmethod/data/repository/FakeInputMethodRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.inputmethod.data.repository
+import android.os.UserHandle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputmethod.data.model.InputMethodModel
import kotlinx.coroutines.flow.Flow
@@ -40,14 +41,15 @@
}
override suspend fun enabledInputMethods(
- userId: Int,
- fetchSubtypes: Boolean,
+ user: UserHandle,
+ fetchSubtypes: Boolean
): Flow<InputMethodModel> {
- return usersToEnabledInputMethods[userId] ?: flowOf()
+ return usersToEnabledInputMethods[user.identifier] ?: flowOf()
}
- override suspend fun selectedInputMethodSubtypes(): List<InputMethodModel.Subtype> =
- selectedInputMethodSubtypes
+ override suspend fun selectedInputMethodSubtypes(
+ user: UserHandle,
+ ): List<InputMethodModel.Subtype> = selectedInputMethodSubtypes
override suspend fun showInputMethodPicker(displayId: Int, showAuxiliarySubtypes: Boolean) {
inputMethodPickerShownDisplayId = displayId
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 c423b62..c2a03d4 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
@@ -16,6 +16,7 @@
package com.android.systemui.keyboard.shortcut
+import android.app.role.mockRoleManager
import android.content.applicationContext
import android.content.res.mainResources
import android.hardware.input.fakeInputManager
@@ -41,6 +42,7 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.model.sysUiState
import com.android.systemui.settings.displayTracker
+import com.android.systemui.settings.fakeUserTracker
var Kosmos.shortcutHelperAppCategoriesShortcutsSource: KeyboardShortcutGroupsSource by
Kosmos.Fixture {
@@ -117,6 +119,8 @@
val Kosmos.shortcutHelperViewModel by
Kosmos.Fixture {
ShortcutHelperViewModel(
+ mockRoleManager,
+ fakeUserTracker,
applicationCoroutineScope,
testDispatcher,
shortcutHelperStateInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 87143ef..727de9e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -22,6 +22,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardDone
@@ -138,6 +139,8 @@
private val _canIgnoreAuthAndReturnToGone = MutableStateFlow(false)
override val canIgnoreAuthAndReturnToGone = _canIgnoreAuthAndReturnToGone.asStateFlow()
+ override val onCameraLaunchDetected = MutableStateFlow(CameraLaunchSourceModel())
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index b5ca964..a95609e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -21,7 +21,6 @@
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -49,7 +48,6 @@
fun create(
featureFlags: FakeFeatureFlags = FakeFeatureFlags(),
repository: FakeKeyguardRepository = FakeKeyguardRepository(),
- commandQueue: FakeCommandQueue = FakeCommandQueue(),
bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
configurationRepository: FakeConfigurationRepository = FakeConfigurationRepository(),
shadeRepository: FakeShadeRepository = FakeShadeRepository(),
@@ -87,7 +85,6 @@
}
return WithDependencies(
repository = repository,
- commandQueue = commandQueue,
featureFlags = featureFlags,
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
@@ -95,7 +92,6 @@
powerInteractor = powerInteractor,
KeyguardInteractor(
repository = repository,
- commandQueue = commandQueue,
powerInteractor = powerInteractor,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(configurationRepository),
@@ -112,7 +108,6 @@
data class WithDependencies(
val repository: FakeKeyguardRepository,
- val commandQueue: FakeCommandQueue,
val featureFlags: FakeFeatureFlags,
val bouncerRepository: FakeKeyguardBouncerRepository,
val configurationRepository: FakeConfigurationRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 81d8f0b..5ab56e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -18,7 +18,6 @@
import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
-import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
@@ -31,7 +30,6 @@
Kosmos.Fixture {
KeyguardInteractor(
repository = keyguardRepository,
- commandQueue = fakeCommandQueue,
powerInteractor = powerInteractor,
bouncerRepository = keyguardBouncerRepository,
configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 0666820..8614fc9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -60,6 +60,7 @@
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shadeController
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.scrimController
@@ -98,6 +99,7 @@
val communalRepository by lazy { kosmos.fakeCommunalSceneRepository }
val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel }
val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
+ val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor }
val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
val keyguardInteractor by lazy { kosmos.keyguardInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt
index d92ace9..9a07c4e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorFactory.kt
@@ -42,6 +42,7 @@
falsingCollector,
screenOffAnimationController,
statusBarStateController,
+ mock(),
)
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
index 8486691..d50091e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/domain/interactor/PowerInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.power.domain.interactor
+import com.android.systemui.camera.cameraGestureHelper
import com.android.systemui.classifier.falsingCollector
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.statusbar.statusBarStateController
@@ -29,5 +30,6 @@
falsingCollector = falsingCollector,
screenOffAnimationController = screenOffAnimationController,
statusBarStateController = statusBarStateController,
+ cameraGestureHelper = { cameraGestureHelper },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index 5568c6c..34e99d3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -20,24 +20,13 @@
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
-import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
val Kosmos.gridLayoutTypeInteractor by
Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository) }
val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
- Kosmos.Fixture {
- mapOf(
- Pair(PartitionedGridLayoutType, partitionedGridLayout),
- Pair(InfiniteGridLayoutType, infiniteGridLayout)
- )
- }
+ Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by
- Kosmos.Fixture {
- mapOf(
- Pair(PartitionedGridLayoutType, noopGridConsistencyInteractor),
- Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)
- )
- }
+ Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
index 6625bb5..9481fca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
@@ -20,7 +20,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.partitionedGridLayout
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
val Kosmos.tileGridViewModel by
@@ -29,7 +29,7 @@
gridLayoutTypeInteractor,
gridLayoutMap,
currentTilesInteractor,
- partitionedGridLayout,
+ infiniteGridLayout,
applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
index c56c56cd..299b22e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt
@@ -17,7 +17,6 @@
package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.overlayShadeViewModel
@@ -27,6 +26,5 @@
shadeInteractor = shadeInteractor,
overlayShadeViewModel = overlayShadeViewModel,
quickSettingsContainerViewModel = quickSettingsContainerViewModel,
- applicationScope = applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index fff3b14..dd93141 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -2,7 +2,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.shared.model.FakeScene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
@@ -18,16 +17,7 @@
)
}
-val Kosmos.fakeScenes by Fixture {
- sceneKeys
- .map { key ->
- FakeScene(
- scope = testScope.backgroundScope,
- key = key,
- )
- }
- .toSet()
-}
+val Kosmos.fakeScenes by Fixture { sceneKeys.map { key -> FakeScene(key) }.toSet() }
val Kosmos.scenes by Fixture { fakeScenes }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
index eeaa9db..64e3526 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeScene.kt
@@ -19,16 +19,12 @@
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
-import kotlinx.coroutines.flow.stateIn
class FakeScene(
- val scope: CoroutineScope,
override val key: SceneKey,
) : Scene {
var isDestinationScenesBeingCollected = false
@@ -40,11 +36,6 @@
.receiveAsFlow()
.onStart { isDestinationScenesBeingCollected = true }
.onCompletion { isDestinationScenesBeingCollected = false }
- .stateIn(
- scope = scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = emptyMap(),
- )
suspend fun setDestinationScenes(value: Map<UserAction, UserActionResult>) {
destinationScenesChannel.send(value)
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
index 989c3a5..2c5a0f4 100644
--- 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
@@ -17,7 +17,6 @@
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
@@ -30,7 +29,6 @@
val Kosmos.shadeSceneViewModel: ShadeSceneViewModel by
Kosmos.Fixture {
ShadeSceneViewModel(
- applicationScope = applicationCoroutineScope,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsSceneAdapter,
brightnessMirrorViewModel = brightnessMirrorViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
index 77d97bb..933ebf0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorKosmos.kt
@@ -18,23 +18,19 @@
import com.android.systemui.dump.dumpManager
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
-import com.android.systemui.util.settings.fakeSettings
var Kosmos.lockScreenMinimalismCoordinator by
Kosmos.Fixture {
LockScreenMinimalismCoordinator(
- bgDispatcher = testDispatcher,
dumpManager = dumpManager,
headsUpInteractor = headsUpNotificationInteractor,
logger = lockScreenMinimalismCoordinatorLogger,
scope = testScope.backgroundScope,
- secureSettings = fakeSettings,
seenNotificationsInteractor = seenNotificationsInteractor,
statusBarStateController = statusBarStateController,
shadeInteractor = shadeInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
index c1e0419..b19e221 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
@@ -16,12 +16,32 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.os.UserHandle
+import android.provider.Settings
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.util.settings.fakeSettings
val Kosmos.seenNotificationsInteractor by Fixture {
SeenNotificationsInteractor(
+ bgDispatcher = testDispatcher,
notificationListRepository = activeNotificationListRepository,
+ secureSettings = fakeSettings,
)
}
+
+var Kosmos.lockScreenShowOnlyUnseenNotificationsSetting: Boolean
+ get() =
+ fakeSettings.getIntForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ UserHandle.USER_CURRENT,
+ ) == 1
+ set(value) {
+ fakeSettings.putIntForUser(
+ Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ if (value) 1 else 2,
+ UserHandle.USER_CURRENT,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index b8dec31..0b309b5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -319,14 +319,9 @@
// NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be
// set, but we do not want to override an existing value that is needed by a specific test.
- val rowFuture: SettableFuture<ExpandableNotificationRow> = SettableFuture.create()
val rowInflaterTask =
RowInflaterTask(mFakeSystemClock, Mockito.mock(RowInflaterTaskLogger::class.java))
- rowInflaterTask.inflate(context, null, entry, MoreExecutors.directExecutor()) { inflatedRow
- ->
- rowFuture.set(inflatedRow)
- }
- val row = rowFuture.get(1, TimeUnit.SECONDS)
+ val row = rowInflaterTask.inflateSynchronously(context, null, entry)
entry.row = row
mIconManager.createIcons(entry)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
index 476b7d8..6417779 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
@@ -38,6 +38,11 @@
public static final Uri CONTENT_URI = Uri.parse("content://settings/fake_global");
+ /**
+ * @deprecated Please use FakeGlobalSettings(testDispatcher) to provide the same dispatcher used
+ * by main test scope.
+ */
+ @Deprecated
public FakeGlobalSettings() {
mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
index df6fc41..35fa2af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettingsKosmos.kt
@@ -18,5 +18,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
-val Kosmos.fakeGlobalSettings: FakeGlobalSettings by Fixture { FakeGlobalSettings() }
+val Kosmos.fakeGlobalSettings: FakeGlobalSettings by Fixture { FakeGlobalSettings(testDispatcher) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index e35da11..e4e2481 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -47,6 +47,11 @@
@UserIdInt
private int mUserId = UserHandle.USER_CURRENT;
+ /**
+ * @deprecated Please use FakeSettings(testDispatcher) to provide the same dispatcher used
+ * by main test scope.
+ */
+ @Deprecated
public FakeSettings() {
mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
index bcb5848..55044bf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettingsKosmos.kt
@@ -18,5 +18,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.settings.fakeUserTracker
-val Kosmos.fakeSettings: FakeSettings by Fixture { FakeSettings() }
+val Kosmos.fakeSettings: FakeSettings by Fixture { FakeSettings(testDispatcher, fakeUserTracker) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
index d391750..0a617d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
@@ -16,10 +16,7 @@
package com.android.systemui.volume.data.repository
-import androidx.annotation.IntRange
import com.android.settingslib.volume.data.repository.AudioSharingRepository
-import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
-import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MIN
import com.android.settingslib.volume.data.repository.GroupIdToVolumes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -27,11 +24,14 @@
class FakeAudioSharingRepository : AudioSharingRepository {
private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ private val mutablePrimaryGroupId: MutableStateFlow<Int> =
+ MutableStateFlow(TEST_GROUP_ID_INVALID)
private val mutableSecondaryGroupId: MutableStateFlow<Int> =
MutableStateFlow(TEST_GROUP_ID_INVALID)
private val mutableVolumeMap: MutableStateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
override val inAudioSharing: Flow<Boolean> = mutableInAudioSharing
+ override val primaryGroupId: StateFlow<Int> = mutablePrimaryGroupId
override val secondaryGroupId: StateFlow<Int> = mutableSecondaryGroupId
override val volumeMap: StateFlow<GroupIdToVolumes> = mutableVolumeMap
@@ -41,6 +41,10 @@
mutableInAudioSharing.value = state
}
+ fun setPrimaryGroupId(groupId: Int) {
+ mutablePrimaryGroupId.value = groupId
+ }
+
fun setSecondaryGroupId(groupId: Int) {
mutableSecondaryGroupId.value = groupId
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
index 03981bb..ce8aba5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorKosmos.kt
@@ -18,12 +18,15 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
import com.android.systemui.volume.data.repository.audioSharingRepository
val Kosmos.audioSharingInteractor by
Kosmos.Fixture {
AudioSharingInteractorImpl(
applicationCoroutineScope,
+ backgroundCoroutineContext,
+ audioVolumeInteractor,
audioSharingRepository,
)
}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index e2eb09f..cc8ccc3 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -182,6 +182,9 @@
// want to verify they're unbundled. The "impl" library above is what
// ships inside the Ravenwood environment to actually drive any API
// access to implementation details.
+// This library needs to be statically linked to mainline tests as well,
+// which need to be able to run on multiple API levels, so we can't use
+// test APIs in this module.
java_library {
name: "ravenwood-junit",
srcs: [
@@ -189,7 +192,7 @@
"junit-stub-src/**/*.java",
"junit-flag-src/**/*.java",
],
- sdk_version: "test_current",
+ sdk_version: "module_current",
static_libs: [
"ravenwood-runtime-common",
"ravenwood-runtime-common-device",
@@ -327,6 +330,11 @@
"services.core.ravenwood-jarjar",
"services.fakes.ravenwood-jarjar",
+ // ICU
+ "core-icu4j-for-host.ravenwood",
+ "icu4j-icudata-jarjar",
+ "icu4j-icutzdata-jarjar",
+
// Provide runtime versions of utils linked in below
"junit",
"truth",
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 68b5aeb..825c91a 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -18,7 +18,7 @@
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.SYSTEM_UID;
-import static android.os.UserHandle.USER_SYSTEM;
+import static android.os.UserHandle.SYSTEM;
import static org.junit.Assert.fail;
@@ -115,7 +115,7 @@
private static final AtomicInteger sNextPid = new AtomicInteger(100);
- int mCurrentUser = USER_SYSTEM;
+ int mCurrentUser = SYSTEM.getIdentifier();
/**
* Unless the test author requests differently, run as "nobody", and give each collection of
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
index 8a2bc1d..f7a59a4b 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManager.java
@@ -26,7 +26,6 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Slog;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -60,6 +59,7 @@
private final Set<AccessibilityHierarchyCheck> mHierarchyChecks;
private final ATFHierarchyBuilder mATFHierarchyBuilder;
private final Set<AccessibilityCheckResultReported> mCachedResults = new HashSet<>();
+
@VisibleForTesting
final A11yCheckerTimer mTimer = new A11yCheckerTimer();
@@ -86,10 +86,8 @@
*/
@RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
public Set<AccessibilityCheckResultReported> maybeRunA11yChecker(
- List<AccessibilityNodeInfo> nodes,
- @Nullable AccessibilityEvent accessibilityEvent,
- ComponentName sourceComponentName,
- @UserIdInt int userId) {
+ List<AccessibilityNodeInfo> nodes, @Nullable String sourceEventClassName,
+ ComponentName a11yServiceComponentName, @UserIdInt int userId) {
if (!shouldRunA11yChecker()) {
return Set.of();
}
@@ -108,14 +106,13 @@
List<AccessibilityHierarchyCheckResult> checkResults = runChecksOnNode(nodeInfo);
Set<AccessibilityCheckResultReported> filteredResults =
AccessibilityCheckerUtils.processResults(nodeInfo, checkResults,
- accessibilityEvent, mPackageManager, sourceComponentName);
+ sourceEventClassName, mPackageManager, a11yServiceComponentName);
allResults.addAll(filteredResults);
}
mCachedResults.addAll(allResults);
} catch (RuntimeException e) {
Slog.e(LOG_TAG, "An unknown error occurred while running a11y checker.", e);
}
-
return allResults;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
index 4171108..fa0fed2 100644
--- a/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
+++ b/services/accessibility/java/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtils.java
@@ -22,7 +22,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Slog;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -96,7 +95,7 @@
static Set<AccessibilityCheckResultReported> processResults(
AccessibilityNodeInfo nodeInfo,
List<AccessibilityHierarchyCheckResult> checkResults,
- @Nullable AccessibilityEvent accessibilityEvent,
+ @Nullable String activityClassName,
PackageManager packageManager,
ComponentName a11yServiceComponentName) {
String appPackageName = nodeInfo.getPackageName().toString();
@@ -110,7 +109,8 @@
.setPackageName(appPackageName)
.setAppVersionCode(getAppVersionCode(packageManager, appPackageName))
.setUiElementPath(nodePath)
- .setActivityName(getActivityName(packageManager, accessibilityEvent))
+ .setActivityName(
+ getActivityName(packageManager, appPackageName, activityClassName))
.setWindowTitle(getWindowTitle(nodeInfo))
.setSourceComponentName(a11yServiceComponentName.flattenToString())
.setSourceVersionCode(
@@ -140,31 +140,23 @@
}
/**
- * Returns the simple class name of the Activity providing the cache update, if available,
+ * Returns the simple class name of the Activity associated with the window, if available,
* or an empty String if not.
*/
@VisibleForTesting
static String getActivityName(
- PackageManager packageManager, @Nullable AccessibilityEvent accessibilityEvent) {
- if (accessibilityEvent == null) {
+ PackageManager packageManager, String packageName, @Nullable String activityClassName) {
+ if (activityClassName == null) {
return "";
}
- CharSequence activityName = accessibilityEvent.getClassName();
- if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
- && accessibilityEvent.getPackageName() != null
- && activityName != null) {
- try {
- // Check class is for a valid Activity.
- packageManager
- .getActivityInfo(
- new ComponentName(accessibilityEvent.getPackageName().toString(),
- activityName.toString()), 0);
- int qualifierEnd = activityName.toString().lastIndexOf('.');
- return activityName.toString().substring(qualifierEnd + 1);
- } catch (PackageManager.NameNotFoundException e) {
- // No need to spam the logs. This is very frequent when the class doesn't match
- // an activity.
- }
+ try {
+ // Check class is for a valid Activity.
+ packageManager.getActivityInfo(new ComponentName(packageName, activityClassName), 0);
+ int qualifierEnd = activityClassName.lastIndexOf('.');
+ return activityClassName.substring(qualifierEnd + 1);
+ } catch (PackageManager.NameNotFoundException e) {
+ // No need to spam the logs. This is very frequent when the class doesn't match
+ // an activity.
}
return "";
}
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index ced7773..11de258 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -130,17 +130,18 @@
}
@Override
- public void sendMessage(int subId, String callingPkg, Uri contentUri, String locationUrl,
- Bundle configOverrides, PendingIntent sentIntent, long messageId,
+ public void sendMessage(int subId, int callingUser, String callingPkg,
+ Uri contentUri, String locationUrl, Bundle configOverrides,
+ PendingIntent sentIntent, long messageId,
String attributionTag) throws RemoteException {
returnPendingIntentWithError(sentIntent);
}
@Override
- public void downloadMessage(int subId, String callingPkg, String locationUrl,
- Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent,
- long messageId, String attributionTag)
- throws RemoteException {
+ public void downloadMessage(int subId, int callingUser, String callingPkg,
+ String locationUrl, Uri contentUri, Bundle configOverrides,
+ PendingIntent downloadedIntent,
+ long messageId, String attributionTag) throws RemoteException {
returnPendingIntentWithError(downloadedIntent);
}
@@ -151,8 +152,9 @@
}
@Override
- public Uri importMultimediaMessage(String callingPkg, Uri contentUri, String messageId,
- long timestampSecs, boolean seen, boolean read) throws RemoteException {
+ public Uri importMultimediaMessage(int callingUser, String callingPkg,
+ Uri contentUri, String messageId, long timestampSecs,
+ boolean seen, boolean read) throws RemoteException {
return null;
}
@@ -187,8 +189,8 @@
}
@Override
- public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
- throws RemoteException {
+ public Uri addMultimediaMessageDraft(int callingUser, String callingPkg,
+ Uri contentUri) throws RemoteException {
return null;
}
@@ -333,9 +335,9 @@
private static final String PHONE_PACKAGE_NAME = "com.android.phone";
@Override
- public void sendMessage(int subId, String callingPkg, Uri contentUri,
- String locationUrl, Bundle configOverrides, PendingIntent sentIntent,
- long messageId, String attributionTag)
+ public void sendMessage(int subId, int callingUser, String callingPkg,
+ Uri contentUri, String locationUrl, Bundle configOverrides,
+ PendingIntent sentIntent, long messageId, String attributionTag)
throws RemoteException {
Slog.d(TAG, "sendMessage() by " + callingPkg);
mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message");
@@ -360,14 +362,15 @@
CarrierMessagingService.SERVICE_INTERFACE,
Intent.FLAG_GRANT_READ_URI_PERMISSION,
subId);
- getServiceGuarded().sendMessage(subId, callingPkg, contentUri, locationUrl,
- configOverrides, sentIntent, messageId, attributionTag);
+ getServiceGuarded().sendMessage(subId, getCallingUserId(), callingPkg, contentUri,
+ locationUrl, configOverrides, sentIntent, messageId, attributionTag);
}
@Override
- public void downloadMessage(int subId, String callingPkg, String locationUrl,
- Uri contentUri, Bundle configOverrides, PendingIntent downloadedIntent,
- long messageId, String attributionTag) throws RemoteException {
+ public void downloadMessage(int subId, int callingUser, String callingPkg,
+ String locationUrl, Uri contentUri, Bundle configOverrides,
+ PendingIntent downloadedIntent, long messageId, String attributionTag)
+ throws RemoteException {
Slog.d(TAG, "downloadMessage() by " + callingPkg);
mContext.enforceCallingPermission(Manifest.permission.RECEIVE_MMS,
"Download MMS message");
@@ -381,8 +384,8 @@
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
subId);
- getServiceGuarded().downloadMessage(subId, callingPkg, locationUrl, contentUri,
- configOverrides, downloadedIntent, messageId, attributionTag);
+ getServiceGuarded().downloadMessage(subId, getCallingUserId(), callingPkg, locationUrl,
+ contentUri, configOverrides, downloadedIntent, messageId, attributionTag);
}
@Override
@@ -399,8 +402,8 @@
}
@Override
- public Uri importMultimediaMessage(String callingPkg, Uri contentUri,
- String messageId, long timestampSecs, boolean seen, boolean read)
+ public Uri importMultimediaMessage(int callingUser, String callingPkg,
+ Uri contentUri, String messageId, long timestampSecs, boolean seen, boolean read)
throws RemoteException {
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
callingPkg, null, null) != AppOpsManager.MODE_ALLOWED) {
@@ -408,8 +411,8 @@
// while writing the TelephonyProvider
return FAKE_MMS_SENT_URI;
}
- return getServiceGuarded().importMultimediaMessage(
- callingPkg, contentUri, messageId, timestampSecs, seen, read);
+ return getServiceGuarded().importMultimediaMessage(getCallingUserId(), callingPkg,
+ contentUri, messageId, timestampSecs, seen, read);
}
@Override
@@ -467,15 +470,16 @@
}
@Override
- public Uri addMultimediaMessageDraft(String callingPkg, Uri contentUri)
- throws RemoteException {
+ public Uri addMultimediaMessageDraft(int callingUser, String callingPkg,
+ Uri contentUri) throws RemoteException {
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
callingPkg, null, null) != AppOpsManager.MODE_ALLOWED) {
// Silently fail AppOps failure due to not being the default SMS app
// while writing the TelephonyProvider
return FAKE_MMS_DRAFT_URI;
}
- return getServiceGuarded().addMultimediaMessageDraft(callingPkg, contentUri);
+ return getServiceGuarded().addMultimediaMessageDraft(getCallingUserId(), callingPkg,
+ contentUri);
}
@Override
@@ -572,4 +576,13 @@
if (info == null) return INVALID_SIM_SLOT_INDEX;
return info.getSimSlotIndex();
}
+
+ /**
+ * Retrieves the calling user id.
+ * @return The id of the calling user.
+ */
+ private int getCallingUserId() {
+ return Binder.getCallingUserHandle().getIdentifier();
+ }
+
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 3633d0f..33cf842 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -55,6 +55,7 @@
import android.telephony.BarringInfo;
import android.telephony.CallQuality;
import android.telephony.CallState;
+import android.telephony.CarrierConfigManager;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.CellSignalStrength;
@@ -426,6 +427,7 @@
private boolean[] mSCBMStarted;
private boolean[] mCarrierRoamingNtnMode = null;
+ private boolean[] mCarrierRoamingNtnEligible = null;
/**
* Per-phone map of precise data connection state. The key of the map is the pair of transport
@@ -726,6 +728,7 @@
mSCBMReason = copyOf(mSCBMReason, mNumPhones);
mSCBMStarted = copyOf(mSCBMStarted, mNumPhones);
mCarrierRoamingNtnMode = copyOf(mCarrierRoamingNtnMode, mNumPhones);
+ mCarrierRoamingNtnEligible = copyOf(mCarrierRoamingNtnEligible, mNumPhones);
// ds -> ss switch.
if (mNumPhones < oldNumPhones) {
cutListToSize(mCellInfo, mNumPhones);
@@ -785,6 +788,7 @@
mSCBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN;
mSCBMStarted[i] = false;
mCarrierRoamingNtnMode[i] = false;
+ mCarrierRoamingNtnEligible[i] = false;
}
}
}
@@ -859,6 +863,7 @@
mSCBMReason = new int[numPhones];
mSCBMStarted = new boolean[numPhones];
mCarrierRoamingNtnMode = new boolean[numPhones];
+ mCarrierRoamingNtnEligible = new boolean[numPhones];
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
@@ -903,6 +908,7 @@
mSCBMReason[i] = TelephonyManager.STOP_REASON_UNKNOWN;
mSCBMStarted[i] = false;
mCarrierRoamingNtnMode[i] = false;
+ mCarrierRoamingNtnEligible[i] = false;
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1518,6 +1524,15 @@
remove(r.binder);
}
}
+ if (events.contains(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED)) {
+ try {
+ r.callback.onCarrierRoamingNtnEligibleStateChanged(
+ mCarrierRoamingNtnEligible[r.phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
}
@@ -3536,6 +3551,53 @@
}
}
+ /**
+ * Notify external listeners that device eligibility to connect to carrier roaming
+ * non-terrestrial network changed.
+ *
+ * @param subId subscription ID.
+ * @param eligible {@code true} when the device is eligible for satellite
+ * communication if all the following conditions are met:
+ * <ul>
+ * <li>Any subscription on the device supports P2P satellite messaging which is defined by
+ * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+ * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to
+ * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li>
+ * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi,
+ * and the hysteresis timer defined by {@link CarrierConfigManager
+ * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired. </li>
+ * </ul>
+ */
+ public void notifyCarrierRoamingNtnEligibleStateChanged(int subId, boolean eligible) {
+ if (!checkNotifyPermission("notifyCarrierRoamingNtnEligibleStateChanged")) {
+ log("notifyCarrierRoamingNtnEligibleStateChanged: caller does not have required "
+ + "permissions.");
+ return;
+ }
+
+ if (VDBG) {
+ log("notifyCarrierRoamingNtnEligibleStateChanged: "
+ + "subId=" + subId + " eligible" + eligible);
+ }
+
+ synchronized (mRecords) {
+ int phoneId = getPhoneIdFromSubId(subId);
+ mCarrierRoamingNtnEligible[phoneId] = eligible;
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ r.callback.onCarrierRoamingNtnEligibleStateChanged(eligible);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -3589,6 +3651,8 @@
pw.println("mECBMStarted=" + mECBMStarted[i]);
pw.println("mSCBMReason=" + mSCBMReason[i]);
pw.println("mSCBMStarted=" + mSCBMStarted[i]);
+ pw.println("mCarrierRoamingNtnMode=" + mCarrierRoamingNtnMode[i]);
+ pw.println("mCarrierRoamingNtnEligible=" + mCarrierRoamingNtnEligible[i]);
// We need to obfuscate package names, and primitive arrays' native toString is ugly
Pair<List<String>, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index f821e00..69478bb 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4616,6 +4616,9 @@
opPackageName,
visibleAccountTypes,
includeUserManagedNotVisible);
+ } catch (SQLiteException e) {
+ Log.w(TAG, "Could not get accounts for user " + userId, e);
+ return new Account[]{};
} finally {
restoreCallingIdentity(identityToken);
}
@@ -4703,12 +4706,17 @@
public Account[] getSharedAccountsAsUser(int userId) {
userId = handleIncomingUser(userId);
- UserAccounts accounts = getUserAccounts(userId);
- synchronized (accounts.dbLock) {
- List<Account> accountList = accounts.accountsDb.getSharedAccounts();
- Account[] accountArray = new Account[accountList.size()];
- accountList.toArray(accountArray);
- return accountArray;
+ try {
+ UserAccounts accounts = getUserAccounts(userId);
+ synchronized (accounts.dbLock) {
+ List<Account> accountList = accounts.accountsDb.getSharedAccounts();
+ Account[] accountArray = new Account[accountList.size()];
+ accountList.toArray(accountArray);
+ return accountArray;
+ }
+ } catch (SQLiteException e) {
+ Log.w(TAG, "Could not get shared accounts for user " + userId, e);
+ return new Account[]{};
}
}
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 47b65eb..1f88657 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -353,8 +353,8 @@
}
/** Called when there is a low memory kill */
- void scheduleNoteLmkdProcKilled(final int pid, final int uid) {
- mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid)
+ void scheduleNoteLmkdProcKilled(final int pid, final int uid, final int rssKb) {
+ mKillHandler.obtainMessage(KillHandler.MSG_LMKD_PROC_KILLED, pid, uid, Long.valueOf(rssKb))
.sendToTarget();
}
@@ -401,9 +401,9 @@
if (lmkd != null) {
updateExistingExitInfoRecordLocked(info, null,
- ApplicationExitInfo.REASON_LOW_MEMORY);
+ ApplicationExitInfo.REASON_LOW_MEMORY, (Long) lmkd.second);
} else if (zygote != null) {
- updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null);
+ updateExistingExitInfoRecordLocked(info, (Integer) zygote.second, null, null);
} else {
scheduleLogToStatsdLocked(info, false);
}
@@ -486,7 +486,7 @@
*/
@GuardedBy("mLock")
private void updateExistingExitInfoRecordLocked(ApplicationExitInfo info,
- Integer status, Integer reason) {
+ Integer status, Integer reason, Long rssKb) {
if (info == null || !isFresh(info.getTimestamp())) {
// if the record is way outdated, don't update it then (because of potential pid reuse)
return;
@@ -513,6 +513,9 @@
immediateLog = true;
}
}
+ if (rssKb != null) {
+ info.setRss(rssKb.longValue());
+ }
scheduleLogToStatsdLocked(info, immediateLog);
}
@@ -523,7 +526,7 @@
*/
@GuardedBy("mLock")
private boolean updateExitInfoIfNecessaryLocked(
- int pid, int uid, Integer status, Integer reason) {
+ int pid, int uid, Integer status, Integer reason, Long rssKb) {
Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
if (k != null) {
uid = k;
@@ -552,7 +555,7 @@
// always be the first one we se as `getExitInfosLocked()` returns them sorted
// by most-recent-first.
isModified[0] = true;
- updateExistingExitInfoRecordLocked(info, status, reason);
+ updateExistingExitInfoRecordLocked(info, status, reason, rssKb);
return FOREACH_ACTION_STOP_ITERATION;
}
return FOREACH_ACTION_NONE;
@@ -1668,11 +1671,11 @@
switch (msg.what) {
case MSG_LMKD_PROC_KILLED:
mAppExitInfoSourceLmkd.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
- null /* status */);
+ null /* status */, (Long) msg.obj /* rss_kb */);
break;
case MSG_CHILD_PROC_DIED:
mAppExitInfoSourceZygote.onProcDied(msg.arg1 /* pid */, msg.arg2 /* uid */,
- (Integer) msg.obj /* status */);
+ (Integer) msg.obj /* status */, null /* rss_kb */);
break;
case MSG_PROC_DIED: {
ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
@@ -1833,7 +1836,7 @@
}
}
- void onProcDied(final int pid, final int uid, final Integer status) {
+ void onProcDied(final int pid, final int uid, final Integer status, final Long rssKb) {
if (DEBUG_PROCESSES) {
Slog.i(TAG, mTag + ": proc died: pid=" + pid + " uid=" + uid
+ ", status=" + status);
@@ -1846,8 +1849,12 @@
// Unlikely but possible: the record has been created
// Let's update it if we could find a ApplicationExitInfo record
synchronized (mLock) {
- if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason)) {
- addLocked(pid, uid, status);
+ if (!updateExitInfoIfNecessaryLocked(pid, uid, status, mPresetReason, rssKb)) {
+ if (rssKb != null) {
+ addLocked(pid, uid, rssKb); // lmkd
+ } else {
+ addLocked(pid, uid, status); // zygote
+ }
}
// Notify any interesed party regarding the lmkd kills
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 8df4e77..e46ab8f 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -124,6 +124,7 @@
import com.android.server.pm.UserManagerInternal;
import com.android.server.power.optimization.Flags;
import com.android.server.power.stats.AggregatedPowerStatsConfig;
+import com.android.server.power.stats.AmbientDisplayPowerStatsProcessor;
import com.android.server.power.stats.AudioPowerStatsProcessor;
import com.android.server.power.stats.BatteryExternalStatsWorker;
import com.android.server.power.stats.BatteryStatsDumpHelperImpl;
@@ -142,6 +143,7 @@
import com.android.server.power.stats.PowerStatsScheduler;
import com.android.server.power.stats.PowerStatsStore;
import com.android.server.power.stats.PowerStatsUidResolver;
+import com.android.server.power.stats.ScreenPowerStatsProcessor;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
import com.android.server.power.stats.VideoPowerStatsProcessor;
import com.android.server.power.stats.WifiPowerStatsProcessor;
@@ -488,6 +490,20 @@
.setProcessor(
new CpuPowerStatsProcessor(mPowerProfile, mCpuScalingPolicies));
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SCREEN)
+ .trackDeviceStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .trackUidStates(
+ AggregatedPowerStatsConfig.STATE_POWER,
+ AggregatedPowerStatsConfig.STATE_SCREEN)
+ .setProcessor(
+ new ScreenPowerStatsProcessor(mPowerProfile));
+
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+ BatteryConsumer.POWER_COMPONENT_SCREEN)
+ .setProcessor(new AmbientDisplayPowerStatsProcessor());
+
config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
.trackDeviceStates(
AggregatedPowerStatsConfig.STATE_POWER,
@@ -636,6 +652,18 @@
BatteryConsumer.POWER_COMPONENT_CPU,
Flags.streamlinedBatteryStats());
+ mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_SCREEN,
+ Flags.streamlinedMiscBatteryStats());
+ mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+ BatteryConsumer.POWER_COMPONENT_SCREEN,
+ Flags.streamlinedMiscBatteryStats());
+
+ mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+ Flags.streamlinedMiscBatteryStats());
+ mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+ BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+ Flags.streamlinedMiscBatteryStats());
+
mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
Flags.streamlinedConnectivityBatteryStats());
mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 4ff1367..afb7bb4 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -538,6 +538,8 @@
if (!pr.hasProvider(cpi.name)) {
checkTime(startTime, "getContentProviderImpl: scheduling install");
pr.installProvider(cpi.name, cpr);
+ mService.mOomAdjuster.unfreezeTemporarily(proc,
+ CachedAppOptimizer.UNFREEZE_REASON_GET_PROVIDER);
try {
thread.scheduleInstallProvider(cpi);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 91b64f8..6bb56c9 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -25,7 +25,6 @@
import android.os.Bundle;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.text.TextFlags;
import android.widget.WidgetFlags;
import com.android.internal.R;
@@ -163,22 +162,6 @@
WidgetFlags.KEY_MAGNIFIER_ASPECT_RATIO, float.class,
WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
- sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
- TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
- TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
- TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
-
- // Register all text aconfig flags.
- for (int i = 0; i < TextFlags.TEXT_ACONFIGS_FLAGS.length; i++) {
- final String flag = TextFlags.TEXT_ACONFIGS_FLAGS[i];
- final boolean defaultValue = TextFlags.TEXT_ACONFIG_DEFAULT_VALUE[i];
- sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
- TextFlags.NAMESPACE,
- flag,
- TextFlags.getKeyForFlag(flag),
- boolean.class,
- defaultValue));
- }
// add other device configs here...
}
private static volatile boolean sDeviceConfigContextEntriesLoaded = false;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 779aabe..726e827 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -951,12 +951,14 @@
try {
switch (inputData.readInt()) {
case LMK_PROCKILL:
- if (receivedLen != 12) {
+ if (receivedLen != 16) {
return false;
}
final int pid = inputData.readInt();
final int uid = inputData.readInt();
- mAppExitInfoTracker.scheduleNoteLmkdProcKilled(pid, uid);
+ final int rssKb = inputData.readInt();
+ mAppExitInfoTracker.scheduleNoteLmkdProcKilled(pid, uid,
+ rssKb);
return true;
case LMK_KILL_OCCURRED:
if (receivedLen
diff --git a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
index f6f23d9..608fb35 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayStatsService.java
@@ -518,18 +518,24 @@
private void logExternalDisplayIdleStarted() {
synchronized (mExternalDisplayStates) {
for (var i = 0; i < mExternalDisplayStates.size(); i++) {
- mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
- KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio);
- if (DEBUG) {
- final int displayId = mExternalDisplayStates.keyAt(i);
- final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
- Slog.d(TAG, "logExternalDisplayIdleStarted"
- + " displayId=" + displayId
- + " currentState=" + state
- + " countOfExternalDisplays=" + (i + 1)
- + " state=" + KEYGUARD
- + " mIsExternalDisplayUsedForAudio="
- + mIsExternalDisplayUsedForAudio);
+ final int displayId = mExternalDisplayStates.keyAt(i);
+ final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
+ // Don't try to stop "connected" session by keyguard event.
+ // There is no purpose to measure how long keyguard is shown while external
+ // display is connected but not used for mirroring or extended display.
+ // Therefore there no need to log this event.
+ if (state != DISCONNECTED_STATE && state != CONNECTED_STATE) {
+ mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
+ KEYGUARD, i + 1, mIsExternalDisplayUsedForAudio);
+ if (DEBUG) {
+ Slog.d(TAG, "logExternalDisplayIdleStarted"
+ + " displayId=" + displayId
+ + " currentState=" + state
+ + " countOfExternalDisplays=" + (i + 1)
+ + " state=" + KEYGUARD
+ + " mIsExternalDisplayUsedForAudio="
+ + mIsExternalDisplayUsedForAudio);
+ }
}
}
}
@@ -540,7 +546,11 @@
for (var i = 0; i < mExternalDisplayStates.size(); i++) {
final int displayId = mExternalDisplayStates.keyAt(i);
final int state = mExternalDisplayStates.get(displayId, DISCONNECTED_STATE);
- if (state == DISCONNECTED_STATE) {
+ // No need to restart "connected" session after keyguard is stopped.
+ // This is because the connection is continuous even if keyguard is shown.
+ // In case in the future keyguard needs to be measured also while display
+ // is not used, then a 'keyguard finished' event needs to be emitted in this case.
+ if (state == DISCONNECTED_STATE || state == CONNECTED_STATE) {
return;
}
mInjector.writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 36a9c80..55de9aa 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1259,9 +1259,9 @@
* @param dragAndDropChannel The input channel associated with the system drag window.
* @return true if drag and drop was successfully started, false otherwise.
*/
- public boolean startDragAndDrop(@NonNull InputChannel fromChannel,
- @NonNull InputChannel dragAndDropChannel) {
- return mNative.transferTouchGesture(fromChannel.getToken(), dragAndDropChannel.getToken(),
+ public boolean startDragAndDrop(@NonNull IBinder fromChannelToken,
+ @NonNull IBinder dragAndDropChannelToken) {
+ return mNative.transferTouchGesture(fromChannelToken, dragAndDropChannelToken,
true /* isDragDrop */);
}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 4993412..1d1a178 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -22,6 +22,8 @@
import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
+import static com.android.hardware.input.Flags.keyboardLayoutManagerMultiUserImeSetup;
+
import android.annotation.AnyThread;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -1066,9 +1068,15 @@
for (InputMethodInfo imeInfo :
inputMethodManagerInternal.getEnabledInputMethodListAsUser(
userId)) {
- for (InputMethodSubtype imeSubtype :
- inputMethodManager.getEnabledInputMethodSubtypeList(
- imeInfo, true /* allowsImplicitlyEnabledSubtypes */)) {
+ final List<InputMethodSubtype> imeSubtypes;
+ if (keyboardLayoutManagerMultiUserImeSetup()) {
+ imeSubtypes = inputMethodManagerInternal.getEnabledInputMethodSubtypeListAsUser(
+ imeInfo.getId(), true /* allowsImplicitlyEnabledSubtypes */, userId);
+ } else {
+ imeSubtypes = inputMethodManager.getEnabledInputMethodSubtypeList(imeInfo,
+ true /* allowsImplicitlyEnabledSubtypes */);
+ }
+ for (InputMethodSubtype imeSubtype : imeSubtypes) {
if (!imeSubtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
continue;
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index 99f4747..b08f917 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -38,10 +38,11 @@
final class AdditionalSubtypeMapRepository {
private static final String TAG = "AdditionalSubtypeMapRepository";
- // TODO(b/352594784): Should we user other lock primitives?
- @GuardedBy("sPerUserMap")
+ private static final Object sMutationLock = new Object();
+
@NonNull
- private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>();
+ private static volatile ImmutableSparseArray<AdditionalSubtypeMap> sPerUserMap =
+ ImmutableSparseArray.empty();
record WriteTask(@UserIdInt int userId, @NonNull AdditionalSubtypeMap subtypeMap,
@NonNull InputMethodMap inputMethodMap) {
@@ -198,7 +199,7 @@
/**
* Returns {@link AdditionalSubtypeMap} for the given user.
*
- * <p>This method is expected be called after {@link #ensureInitializedAndGet(int)}. Otherwise
+ * <p>This method is expected be called after {@link #initializeIfNecessary(int)}. Otherwise
* {@link AdditionalSubtypeMap#EMPTY_MAP} will be returned.</p>
*
* @param userId the user to be queried about
@@ -207,10 +208,7 @@
@AnyThread
@NonNull
static AdditionalSubtypeMap get(@UserIdInt int userId) {
- final AdditionalSubtypeMap map;
- synchronized (sPerUserMap) {
- map = sPerUserMap.get(userId);
- }
+ final AdditionalSubtypeMap map = sPerUserMap.get(userId);
if (map == null) {
Slog.e(TAG, "get(userId=" + userId + ") is called before loadInitialDataAndGet()."
+ " Returning an empty map");
@@ -220,28 +218,24 @@
}
/**
- * Ensures that {@link AdditionalSubtypeMap} is initialized for the given user. Load it from
- * the persistent storage if {@link #putAndSave(int, AdditionalSubtypeMap, InputMethodMap)} has
- * not been called yet.
+ * Ensures that {@link AdditionalSubtypeMap} is initialized for the given user.
*
* @param userId the user to be initialized
- * @return {@link AdditionalSubtypeMap} that is associated with the given user. If
- * {@link #putAndSave(int, AdditionalSubtypeMap, InputMethodMap)} is already called
- * then the given {@link AdditionalSubtypeMap}.
*/
@AnyThread
@NonNull
- static AdditionalSubtypeMap ensureInitializedAndGet(@UserIdInt int userId) {
- final var map = AdditionalSubtypeUtils.load(userId);
- synchronized (sPerUserMap) {
- final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
- // If putAndSave() has already been called, then use it.
- if (previous != null) {
- return previous;
- }
- sPerUserMap.put(userId, map);
+ static void initializeIfNecessary(@UserIdInt int userId) {
+ if (sPerUserMap.contains(userId)) {
+ // Fast-pass. If putAndSave() is already called, then do nothing.
+ return;
}
- return map;
+ final var map = AdditionalSubtypeUtils.load(userId);
+ synchronized (sMutationLock) {
+ // Check the condition again.
+ if (!sPerUserMap.contains(userId)) {
+ sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, map);
+ }
+ }
}
/**
@@ -255,12 +249,8 @@
@AnyThread
static void putAndSave(@UserIdInt int userId, @NonNull AdditionalSubtypeMap map,
@NonNull InputMethodMap inputMethodMap) {
- synchronized (sPerUserMap) {
- final AdditionalSubtypeMap previous = sPerUserMap.get(userId);
- if (previous == map) {
- return;
- }
- sPerUserMap.put(userId, map);
+ synchronized (sMutationLock) {
+ sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, map);
sWriter.scheduleWriteTask(userId, map, inputMethodMap);
}
}
@@ -277,9 +267,9 @@
@AnyThread
static void remove(@UserIdInt int userId) {
- synchronized (sPerUserMap) {
+ synchronized (sMutationLock) {
sWriter.onUserRemoved(userId);
- sPerUserMap.remove(userId);
+ sPerUserMap = sPerUserMap.cloneWithRemoveOrSelf(userId);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
index a7280e6..7f7ae10 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -154,7 +154,7 @@
void reportPerceptibleAsync(IBinder windowToken, boolean perceptible);
@PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
- void removeImeSurface();
+ void removeImeSurface(int displayId);
void removeImeSurfaceFromWindowAsync(IBinder windowToken);
@@ -384,10 +384,10 @@
@EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
@Override
- public void removeImeSurface() {
+ public void removeImeSurface(int displayId) {
super.removeImeSurface_enforcePermission();
- mCallback.removeImeSurface();
+ mCallback.removeImeSurface(displayId);
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 7ebf595..cdea6ff 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -36,8 +36,10 @@
import static com.android.server.inputmethod.InputMethodManagerService.computeImeDisplayIdForTarget;
import android.accessibilityservice.AccessibilityService;
+import android.annotation.AnyThread;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.res.Configuration;
import android.os.Binder;
@@ -83,6 +85,7 @@
* A map used to track the requested IME target window and its state. The key represents the
* token of the window and the value is the corresponding IME window state.
*/
+ @GuardedBy("ImfLock.class")
private final WeakHashMap<IBinder, ImeTargetWindowState> mRequestWindowStateMap =
new WeakHashMap<>();
@@ -93,6 +96,7 @@
* @see InputMethodManager#HIDE_IMPLICIT_ONLY that system will not hide IME when the value is
* {@code true}.
*/
+ @GuardedBy("ImfLock.class")
boolean mRequestedShowExplicitly;
/**
@@ -101,25 +105,39 @@
* @see InputMethodManager#SHOW_FORCED
* @see InputMethodManager#HIDE_NOT_ALWAYS
*/
+ @GuardedBy("ImfLock.class")
boolean mShowForced;
/**
* Set if we last told the input method to show itself.
*/
+ @GuardedBy("ImfLock.class")
private boolean mInputShown;
/**
* Set if we called
* {@link com.android.server.wm.ImeTargetVisibilityPolicy#showImeScreenshot(IBinder, int)}.
*/
+ @GuardedBy("ImfLock.class")
private boolean mRequestedImeScreenshot;
/** The window token of the current visible IME layering target overlay. */
+ @GuardedBy("ImfLock.class")
private IBinder mCurVisibleImeLayeringOverlay;
/** The window token of the current visible IME input target. */
+ @GuardedBy("ImfLock.class")
private IBinder mCurVisibleImeInputTarget;
+ /**
+ * 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.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ private IBinder mLastImeTargetWindow;
+
/** Represent the invalid IME visibility state */
public static final int STATE_INVALID = -1;
@@ -203,25 +221,32 @@
public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken,
@WindowManager.LayoutParams.WindowType int windowType, boolean visible,
boolean removed) {
- mCurVisibleImeLayeringOverlay =
- // Ignoring the starting window since it's ok to cover the IME target
- // window in temporary without affecting the IME visibility.
- (visible && !removed && windowType != TYPE_APPLICATION_STARTING)
+ // Ignoring the starting window since it's ok to cover the IME target
+ // window in temporary without affecting the IME visibility.
+ final var overlay = (visible && !removed && windowType != TYPE_APPLICATION_STARTING)
? overlayWindowToken : null;
+ synchronized (ImfLock.class) {
+ mCurVisibleImeLayeringOverlay = overlay;
+
+ }
}
@Override
public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
boolean visibleRequested, boolean removed) {
- if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested || removed)
- && mCurVisibleImeLayeringOverlay != null) {
- final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
- final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
- ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */);
- mService.onApplyImeVisibilityFromComputer(imeInputTarget, statsToken,
- new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason));
+ synchronized (ImfLock.class) {
+ if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested
+ || removed)
+ && mCurVisibleImeLayeringOverlay != null) {
+ final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
+ final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+ ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */);
+ mService.onApplyImeVisibilityFromComputerLocked(imeInputTarget, statsToken,
+ new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason));
+ }
+ mCurVisibleImeInputTarget =
+ (visibleRequested && !removed) ? imeInputTarget : null;
}
- mCurVisibleImeInputTarget = (visibleRequested && !removed) ? imeInputTarget : null;
}
});
}
@@ -232,6 +257,7 @@
* @param statsToken The token tracking the current IME request.
* @return {@code true} when the show request can proceed.
*/
+ @GuardedBy("ImfLock.class")
boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken,
@InputMethodManager.ShowFlags int showFlags) {
if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) {
@@ -258,6 +284,7 @@
* @param statsToken The token tracking the current IME request.
* @return {@code true} when the hide request can proceed.
*/
+ @GuardedBy("ImfLock.class")
boolean canHideIme(@NonNull ImeTracker.Token statsToken,
@InputMethodManager.HideFlags int hideFlags) {
if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
@@ -279,6 +306,7 @@
* Returns the show flags for IME. This translates from {@link InputMethodManager.ShowFlags}
* to {@link InputMethod.ShowFlags}.
*/
+ @GuardedBy("ImfLock.class")
@InputMethod.ShowFlags
int getShowFlagsForInputMethodServiceOnly() {
int flags = 0;
@@ -294,6 +322,7 @@
* Returns the show flags for IMM. This translates from {@link InputMethod.ShowFlags}
* to {@link InputMethodManager.ShowFlags}.
*/
+ @GuardedBy("ImfLock.class")
@InputMethodManager.ShowFlags
int getShowFlags() {
int flags = 0;
@@ -305,12 +334,14 @@
return flags;
}
+ @GuardedBy("ImfLock.class")
void clearImeShowFlags() {
mRequestedShowExplicitly = false;
mShowForced = false;
mInputShown = false;
}
+ @GuardedBy("ImfLock.class")
int computeImeDisplayId(@NonNull ImeTargetWindowState state, int displayId) {
final int displayToShowIme = computeImeDisplayIdForTarget(displayId, mImeDisplayValidator);
state.setImeDisplayId(displayToShowIme);
@@ -328,6 +359,7 @@
* visibility state, it could be {@link #STATE_SHOW_IME} or
* {@link #STATE_HIDE_IME}.
*/
+ @GuardedBy("ImfLock.class")
void requestImeVisibility(IBinder windowToken, boolean showIme) {
ImeTargetWindowState state = getOrCreateWindowState(windowToken);
if (!mPolicy.mPendingA11yRequestingHideKeyboard) {
@@ -343,6 +375,7 @@
setWindowStateInner(windowToken, state);
}
+ @GuardedBy("ImfLock.class")
ImeTargetWindowState getOrCreateWindowState(IBinder windowToken) {
ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
if (state == null) {
@@ -351,11 +384,13 @@
return state;
}
+ @GuardedBy("ImfLock.class")
ImeTargetWindowState getWindowStateOrNull(IBinder windowToken) {
ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
return state;
}
+ @GuardedBy("ImfLock.class")
void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
if (state != null && newState.hasEditorFocused()
@@ -367,6 +402,7 @@
setWindowStateInner(windowToken, newState);
}
+ @GuardedBy("ImfLock.class")
private void setWindowStateInner(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
if (DEBUG) Slog.d(TAG, "setWindowStateInner, windowToken=" + windowToken
+ ", state=" + newState);
@@ -391,6 +427,7 @@
}
}
+ @GuardedBy("ImfLock.class")
ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible) {
// TODO: Output the request IME visibility state according to the requested window state
final int softInputVisibility = state.mSoftInputModeState & SOFT_INPUT_MASK_STATE;
@@ -452,8 +489,7 @@
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
// Do nothing but preserving the last IME requested visibility state.
- final ImeTargetWindowState lastState =
- getWindowStateOrNull(mService.mLastImeTargetWindow);
+ final ImeTargetWindowState lastState = getWindowStateOrNull(mLastImeTargetWindow);
if (lastState != null) {
state.setRequestedImeVisible(lastState.mRequestedImeVisible);
}
@@ -540,6 +576,7 @@
return null;
}
+ @GuardedBy("ImfLock.class")
@VisibleForTesting
ImeVisibilityResult onInteractiveChanged(IBinder windowToken, boolean interactive) {
final ImeTargetWindowState state = getWindowStateOrNull(windowToken);
@@ -568,6 +605,7 @@
return userData.mImeBindingState.mFocusedWindow;
}
+ @GuardedBy("ImfLock.class")
IBinder getWindowTokenFrom(ImeTargetWindowState windowState) {
for (IBinder windowToken : mRequestWindowStateMap.keySet()) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
@@ -578,6 +616,7 @@
return null;
}
+ @GuardedBy("ImfLock.class")
boolean shouldRestoreImeVisibility(@NonNull ImeTargetWindowState state) {
final int softInputMode = state.getSoftInputModeState();
switch (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
@@ -591,14 +630,28 @@
return mWindowManagerInternal.shouldRestoreImeVisibility(getWindowTokenFrom(state));
}
+ @GuardedBy("ImfLock.class")
boolean isInputShown() {
return mInputShown;
}
+ @GuardedBy("ImfLock.class")
void setInputShown(boolean inputShown) {
mInputShown = inputShown;
}
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ IBinder getLastImeTargetWindow() {
+ return mLastImeTargetWindow;
+ }
+
+ @GuardedBy("ImfLock.class")
+ void setLastImeTargetWindow(@Nullable IBinder imeTargetWindow) {
+ mLastImeTargetWindow = imeTargetWindow;
+ }
+
+ @GuardedBy("ImfLock.class")
void dumpDebug(ProtoOutputStream proto, long fieldId) {
proto.write(SHOW_EXPLICITLY_REQUESTED, mRequestedShowExplicitly);
proto.write(SHOW_FORCED, mShowForced);
@@ -607,12 +660,14 @@
proto.write(INPUT_SHOWN, mInputShown);
}
+ @GuardedBy("ImfLock.class")
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
final Printer p = new PrintWriterPrinter(pw);
p.println(prefix + "mRequestedShowExplicitly=" + mRequestedShowExplicitly
+ " mShowForced=" + mShowForced);
p.println(prefix + "mImeHiddenByDisplayPolicy=" + mPolicy.isImeHiddenByDisplayPolicy());
p.println(prefix + "mInputShown=" + mInputShown);
+ p.println(prefix + "mLastImeTargetWindow=" + mLastImeTargetWindow);
}
/**
@@ -629,12 +684,14 @@
*
* This prevents the IME from showing when it otherwise may have shown.
*/
+ @GuardedBy("ImfLock.class")
private boolean mImeHiddenByDisplayPolicy;
/**
* Set when the accessibility service requests to hide IME by
* {@link AccessibilityService.SoftKeyboardController#setShowMode}
*/
+ @GuardedBy("ImfLock.class")
private boolean mA11yRequestingNoSoftKeyboard;
/**
@@ -643,16 +700,20 @@
* {@link android.provider.Settings.Secure#ACCESSIBILITY_SOFT_KEYBOARD_MODE} without
* changing the requested IME visible state.
*/
+ @GuardedBy("ImfLock.class")
private boolean mPendingA11yRequestingHideKeyboard;
+ @GuardedBy("ImfLock.class")
void setImeHiddenByDisplayPolicy(boolean hideIme) {
mImeHiddenByDisplayPolicy = hideIme;
}
+ @GuardedBy("ImfLock.class")
boolean isImeHiddenByDisplayPolicy() {
return mImeHiddenByDisplayPolicy;
}
+ @GuardedBy("ImfLock.class")
void setA11yRequestNoSoftKeyboard(int keyboardShowMode) {
mA11yRequestingNoSoftKeyboard =
(keyboardShowMode & AccessibilityService.SHOW_MODE_MASK) == SHOW_MODE_HIDDEN;
@@ -661,11 +722,13 @@
}
}
+ @GuardedBy("ImfLock.class")
boolean isA11yRequestNoSoftKeyboard() {
return mA11yRequestingNoSoftKeyboard;
}
}
+ @GuardedBy("ImfLock.class")
ImeVisibilityPolicy getImePolicy() {
return mPolicy;
}
@@ -721,63 +784,78 @@
/**
* Set if the client has asked for the input method to be shown.
*/
+ @GuardedBy("ImfLock.class")
private boolean mRequestedImeVisible;
/**
* A identifier for knowing the requester of {@link InputMethodManager#showSoftInput} or
* {@link InputMethodManager#hideSoftInputFromWindow}.
*/
+ @GuardedBy("ImfLock.class")
private IBinder mRequestImeToken;
/**
* The IME target display id for which the latest startInput was called.
*/
+ @GuardedBy("ImfLock.class")
private int mImeDisplayId = DEFAULT_DISPLAY;
+ @AnyThread
boolean hasImeFocusChanged() {
return mImeFocusChanged;
}
+ @AnyThread
boolean hasEditorFocused() {
return mHasFocusedEditor;
}
+ @AnyThread
boolean isStartInputByGainFocus() {
return mIsStartInputByGainFocus;
}
+ @AnyThread
int getSoftInputModeState() {
return mSoftInputModeState;
}
+ @AnyThread
int getWindowFlags() {
return mWindowFlags;
}
+ @AnyThread
int getToolType() {
return mToolType;
}
+ @GuardedBy("ImfLock.class")
private void setImeDisplayId(int imeDisplayId) {
mImeDisplayId = imeDisplayId;
}
+ @GuardedBy("ImfLock.class")
int getImeDisplayId() {
return mImeDisplayId;
}
+ @GuardedBy("ImfLock.class")
private void setRequestedImeVisible(boolean requestedImeVisible) {
mRequestedImeVisible = requestedImeVisible;
}
+ @GuardedBy("ImfLock.class")
boolean isRequestedImeVisible() {
return mRequestedImeVisible;
}
+ @GuardedBy("ImfLock.class")
void setRequestImeToken(IBinder token) {
mRequestImeToken = token;
}
+ @GuardedBy("ImfLock.class")
IBinder getRequestImeToken() {
return mRequestImeToken;
}
diff --git a/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java b/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java
new file mode 100644
index 0000000..382aa8a
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImmutableSparseArray.java
@@ -0,0 +1,183 @@
+/*
+ * 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.inputmethod;
+
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.function.Consumer;
+
+/**
+ * A holder object to expose {@link SparseArray} to multiple threads in a thread-safe manner through
+ * "final Field Semantics" defined in JLS 17.5, with only exposing thread-safe methods such as
+ * {@link SparseArray#get(int)} and {@link SparseArray#size()} from {@link SparseArray}, and with
+ * adding clone-with-update style methods {@link #cloneWithPutOrSelf(int, Object)} and
+ * {@link #cloneWithRemoveOrSelf(int)} instead of exposing mutation methods.
+ *
+ * @param <E> Type of the element
+ */
+final class ImmutableSparseArray<E> {
+ @NonNull
+ private final SparseArray<E> mArray;
+
+ private static final ImmutableSparseArray<Object> EMPTY =
+ new ImmutableSparseArray<>(new SparseArray<>());
+
+ /**
+ * Returns an empty {@link ImmutableSparseArray} instance.
+ *
+ * @return An empty {@link ImmutableSparseArray} instance.
+ * @param <T> Type of the element
+ */
+ @SuppressWarnings("unchecked")
+ @AnyThread
+ @NonNull
+ static <T> ImmutableSparseArray<T> empty() {
+ return (ImmutableSparseArray<T>) EMPTY;
+ }
+
+ private ImmutableSparseArray(@NonNull SparseArray<E> array) {
+ mArray = array;
+ }
+
+ /**
+ * @return the size of this array
+ */
+ @AnyThread
+ int size() {
+ return mArray.size();
+ }
+
+ /**
+ * Returns the key of the specified index.
+ *
+ * @return the key of the specified index
+ * @throws ArrayIndexOutOfBoundsException when the index is out of range
+ */
+ @AnyThread
+ int keyAt(int index) {
+ return mArray.keyAt(index);
+ }
+
+ /**
+ * Returns the value of the specified index.
+ *
+ * @return the value of the specified index
+ * @throws ArrayIndexOutOfBoundsException when the index is out of range
+ */
+ @AnyThread
+ @Nullable
+ public E valueAt(int index) {
+ return mArray.valueAt(index);
+ }
+
+ /**
+ * Returns the index of the specified key.
+ *
+ * @return the index of the specified key if exists. Otherwise {@code -1}
+ */
+ @AnyThread
+ int indexOfKey(int key) {
+ return mArray.indexOfKey(key);
+ }
+
+ /**
+ * Returns {@code true} if the given {@code key} exists.
+ *
+ * @param key the key to be queried
+ * @return {@code true} if the given {@code key} exists
+ */
+ @AnyThread
+ boolean contains(int key) {
+ return mArray.contains(key);
+ }
+
+ /**
+ * Returns the value associated with the {@code key}.
+ *
+ * @param key the key to be queried
+ * @return the value associated with the {@code key} if exists. Otherwise {@code null}
+ */
+ @AnyThread
+ @Nullable
+ E get(int key) {
+ return mArray.get(key);
+ }
+
+ /**
+ * Run {@link Consumer} for each value.
+ *
+ * @param consumer {@link Consumer} to be called back
+ */
+ @AnyThread
+ void forEach(@NonNull Consumer<E> consumer) {
+ final int size = mArray.size();
+ for (int i = 0; i < size; ++i) {
+ consumer.accept(mArray.valueAt(i));
+ }
+ }
+
+ /**
+ * Returns an instance of {@link ImmutableSparseArray} that has the given key and value on top
+ * of items cloned from this instance.
+ *
+ * @param key the key to be added
+ * @param value the value to be added
+ * @return the same {@link ImmutableSparseArray} instance if there is actually no update.
+ * Otherwise, a new instance of {@link ImmutableSparseArray}
+ */
+ @AnyThread
+ @NonNull
+ ImmutableSparseArray<E> cloneWithPutOrSelf(int key, @Nullable E value) {
+ final var prevKeyIndex = mArray.indexOfKey(key);
+ if (prevKeyIndex >= 0) {
+ final var prevValue = mArray.valueAt(prevKeyIndex);
+ if (prevValue == value) {
+ return this;
+ }
+ }
+ final var clone = mArray.clone();
+ clone.put(key, value);
+ return new ImmutableSparseArray<>(clone);
+ }
+
+ /**
+ * Returns an instance of {@link ImmutableSparseArray} that does not have the given key on top
+ * of items cloned from this instance.
+ *
+ * @param key the key to be removed
+ * @return the same {@link ImmutableSparseArray} instance if there is actually no update.
+ * Otherwise, a new instance of {@link ImmutableSparseArray}
+ */
+ @AnyThread
+ @NonNull
+ ImmutableSparseArray<E> cloneWithRemoveOrSelf(int key) {
+ final int index = indexOfKey(key);
+ if (index < 0) {
+ return this;
+ }
+ if (mArray.size() == 1) {
+ return empty();
+ }
+ final var clone = mArray.clone();
+ clone.remove(key);
+ return new ImmutableSparseArray<>(clone);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 9837ab1..03cbab5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -463,7 +463,7 @@
// should now try to restart the service for us.
mLastBindTime = SystemClock.uptimeMillis();
clearCurMethodAndSessions();
- mService.clearInputShownLocked();
+ mService.mVisibilityStateComputer.setInputShown(false);
mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME, mUserId);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 691de04..fb0f658 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -81,6 +81,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.input.InputManager;
import android.inputmethodservice.InputMethodService;
@@ -258,7 +259,6 @@
private static final int MSG_HIDE_ALL_INPUT_METHODS = 1035;
private static final int MSG_REMOVE_IME_SURFACE = 1060;
private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061;
- private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070;
private static final int MSG_RESET_HANDWRITING = 1090;
private static final int MSG_START_HANDWRITING = 1100;
@@ -271,7 +271,6 @@
private static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
- private static final int MSG_SYSTEM_UNLOCK_USER = 5000;
private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010;
private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
@@ -306,6 +305,28 @@
private final String[] mNonPreemptibleInputMethods;
/**
+ * Whether the new Input Method Switcher menu is enabled.
+ *
+ * @see #shouldEnableNewInputMethodSwitcherMenu
+ */
+ @SharedByAllUsersField
+ private final boolean mNewInputMethodSwitcherMenuEnabled;
+
+ /**
+ * Returns {@code true} if the new Input Method Switcher menu is enabled. This will be
+ * {@code false} for watches and small screen devices.
+ *
+ * @param context the context to check the device configuration for.
+ */
+ private static boolean shouldEnableNewInputMethodSwitcherMenu(@NonNull Context context) {
+ final boolean isWatch = context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_WATCH);
+ final boolean isSmallScreen = (context.getResources().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_SMALL;
+ return Flags.imeSwitcherRevamp() && !isWatch && !isSmallScreen;
+ }
+
+ /**
* See {@link #shouldEnableConcurrentMultiUserMode(Context)} about when set to be {@code true}.
*/
@SharedByAllUsersField
@@ -340,6 +361,35 @@
return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentUserId;
}
+ /**
+ * Figures out the target IME user ID associated with the given {@code displayId}.
+ *
+ * @param displayId the display ID to be queried about
+ * @return User ID to be used for this {@code displayId}.
+ */
+ @GuardedBy("ImfLock.class")
+ @UserIdInt
+ private int resolveImeUserIdFromDisplayIdLocked(int displayId) {
+ return mConcurrentMultiUserModeEnabled
+ ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentUserId;
+ }
+
+ /**
+ * Figures out the target IME user ID associated with the given {@code windowToken}.
+ *
+ * @param windowToken the Window token to be queried about
+ * @return User ID to be used for this {@code displayId}.
+ */
+ @GuardedBy("ImfLock.class")
+ @UserIdInt
+ private int resolveImeUserIdFromWindowLocked(@NonNull IBinder windowToken) {
+ if (mConcurrentMultiUserModeEnabled) {
+ final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
+ return mUserManagerInternal.getUserAssignedToDisplay(displayId);
+ }
+ return mCurrentUserId;
+ }
+
final Context mContext;
final Resources mRes;
private final Handler mHandler;
@@ -373,7 +423,7 @@
@GuardedBy("ImfLock.class")
@MultiUserUnawareField
@NonNull
- private final ImeVisibilityStateComputer mVisibilityStateComputer;
+ final ImeVisibilityStateComputer mVisibilityStateComputer;
@GuardedBy("ImfLock.class")
@SharedByAllUsersField
@@ -496,14 +546,6 @@
}
/**
- * 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.
- */
- @MultiUserUnawareField
- IBinder mLastImeTargetWindow;
-
- /**
* Map of window perceptible states indexed by their associated window tokens.
*
* The value {@code true} indicates that IME has not been mostly hidden via
@@ -572,7 +614,7 @@
private void onSecureSettingsChangedLocked(@NonNull String key, @UserIdInt int userId) {
switch (key) {
case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: {
- if (!Flags.imeSwitcherRevamp()) {
+ if (!mNewInputMethodSwitcherMenuEnabled) {
if (userId == mCurrentUserId) {
mMenuController.updateKeyboardFromSettingsLocked();
}
@@ -641,7 +683,7 @@
}
}
}
- if (Flags.imeSwitcherRevamp()) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
synchronized (ImfLock.class) {
final var bindingController = getInputMethodBindingController(senderUserId);
mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(),
@@ -945,7 +987,7 @@
// TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
// additional subtypes in switchUserOnHandlerLocked().
final ServiceThread thread = new ServiceThread(HANDLER_THREAD_NAME,
- Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+ Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
thread.start();
final ServiceThread ioThread = new ServiceThread(PACKAGE_MONITOR_THREAD_NAME,
@@ -1020,10 +1062,24 @@
@Override
public void onUserUnlocking(@NonNull TargetUser user) {
- // Called on ActivityManager thread.
- SecureSettingsWrapper.onUserUnlocking(user.getUserIdentifier());
- mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, user.getUserIdentifier(), 0)
- .sendToTarget();
+ // Called on ActivityManager thread. Do not block the calling thread.
+ final int userId = user.getUserIdentifier();
+ SecureSettingsWrapper.onUserUnlocking(userId);
+ mService.mIoHandler.post(() -> {
+ final var settings = queryInputMethodServicesInternal(mService.mContext, userId,
+ AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ synchronized (ImfLock.class) {
+ if (!mService.mSystemReady) {
+ return;
+ }
+ // We need to rebuild IMEs.
+ mService.postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */,
+ userId);
+ mService.updateInputMethodsFromSettingsLocked(true /* enabledChanged */,
+ userId);
+ }
+ });
}
@Override
@@ -1033,10 +1089,8 @@
SecureSettingsWrapper.onUserStarting(userId);
mService.mIoHandler.post(() -> {
synchronized (ImfLock.class) {
- if (mService.mConcurrentMultiUserModeEnabled) {
- if (mService.mCurrentUserId != userId && mService.mSystemReady) {
- mService.initializeVisibleBackgroundUserLocked(userId);
- }
+ if (mService.mSystemReady) {
+ mService.onUserReadyLocked(userId);
}
}
});
@@ -1052,8 +1106,8 @@
for (int userId : userIds) {
Slog.d(TAG, "Start initialization for user=" + userId);
- final var additionalSubtypeMap =
- AdditionalSubtypeMapRepository.ensureInitializedAndGet(userId);
+ AdditionalSubtypeMapRepository.initializeIfNecessary(userId);
+ final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
final var settings = InputMethodManagerService.queryInputMethodServicesInternal(
context, userId, additionalSubtypeMap,
DirectBootAwareness.AUTO).getMethodMap();
@@ -1074,23 +1128,6 @@
}
}
- void onUnlockUser(@UserIdInt int userId) {
- synchronized (ImfLock.class) {
- if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId);
- }
- if (!mSystemReady) {
- return;
- }
- final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
- userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
- InputMethodSettingsRepository.put(userId, newSettings);
- // We need to rebuild IMEs.
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
- updateInputMethodsFromSettingsLocked(true /* enabledChanged */, userId);
- }
- }
-
@GuardedBy("ImfLock.class")
void scheduleSwitchUserTaskLocked(@UserIdInt int userId,
@Nullable IInputMethodClientInvoker clientToBeReset) {
@@ -1099,7 +1136,7 @@
mUserSwitchHandlerTask.mClientToBeReset = clientToBeReset;
return;
}
- mHandler.removeCallbacks(mUserSwitchHandlerTask);
+ mIoHandler.removeCallbacks(mUserSwitchHandlerTask);
}
// Hide soft input before user switch task since switch task may block main handler a while
// and delayed the hideCurrentInputLocked().
@@ -1109,7 +1146,7 @@
final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
clientToBeReset);
mUserSwitchHandlerTask = task;
- mHandler.post(task);
+ mIoHandler.post(task);
}
@VisibleForTesting
@@ -1137,6 +1174,7 @@
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
+ mNewInputMethodSwitcherMenuEnabled = shouldEnableNewInputMethodSwitcherMenu(mContext);
mShowOngoingImeSwitcherForPhones = false;
@@ -1149,7 +1187,7 @@
: bindingControllerFactory);
mMenuController = new InputMethodMenuController(this);
- mMenuControllerNew = Flags.imeSwitcherRevamp()
+ mMenuControllerNew = mNewInputMethodSwitcherMenuEnabled
? new InputMethodMenuControllerNew() : null;
mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
mVisibilityApplier = new DefaultImeVisibilityApplier(this);
@@ -1390,30 +1428,32 @@
UserHandle.ALL, broadcastFilterForAllUsers, null, null,
Context.RECEIVER_EXPORTED);
- final String defaultImiId = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId);
- final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- final var settings = InputMethodSettingsRepository.get(currentUserId);
- postInputMethodSettingUpdatedLocked(
- !imeSelectedOnBoot /* resetDefaultEnabledIme */, currentUserId);
- updateFromSettingsLocked(true, currentUserId);
- InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
- getPackageManagerForUser(mContext, currentUserId),
- settings.getEnabledInputMethodList());
-
AdditionalSubtypeMapRepository.startWriterThread();
- if (mConcurrentMultiUserModeEnabled) {
- for (int userId : mUserManagerInternal.getUserIds()) {
- if (userId != mCurrentUserId) {
- initializeVisibleBackgroundUserLocked(userId);
- }
- }
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ onUserReadyLocked(userId);
}
}
}
}
+ @GuardedBy("ImfLock.class")
+ void onUserReadyLocked(@UserIdInt int userId) {
+ if (!mUserManagerInternal.isUserRunning(userId)) {
+ return;
+ }
+
+ final String defaultImiId = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
+ final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
+ final var settings = InputMethodSettingsRepository.get(userId);
+ postInputMethodSettingUpdatedLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */,
+ userId);
+ updateFromSettingsLocked(true, userId);
+ InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
+ getPackageManagerForUser(mContext, userId), settings.getEnabledInputMethodList());
+ }
+
void registerImeRequestedChangedListener() {
mWindowManagerInternal.setOnImeRequestedChangedListener(
(windowToken, imeVisible, statsToken) -> {
@@ -1768,7 +1808,7 @@
ImeTracker.PHASE_SERVER_WAIT_IME);
userData.mCurStatsToken = null;
// TODO: Make mMenuController multi-user aware
- if (Flags.imeSwitcherRevamp()) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
} else {
mMenuController.hideInputMethodMenuLocked();
@@ -1811,17 +1851,6 @@
}
@GuardedBy("ImfLock.class")
- void clearInputShownLocked() {
- mVisibilityStateComputer.setInputShown(false);
- }
-
- @GuardedBy("ImfLock.class")
- @Override
- public boolean isInputShownLocked() {
- return mVisibilityStateComputer.isInputShown();
- }
-
- @GuardedBy("ImfLock.class")
private boolean isShowRequestedForCurrentWindow(@UserIdInt int userId) {
final var userData = getUserData(userId);
// TODO(b/349904272): Make mVisibilityStateComputer multi-user aware
@@ -2594,7 +2623,7 @@
if (!mShowOngoingImeSwitcherForPhones) return false;
// When the IME switcher dialog is shown, the IME switcher button should be hidden.
// TODO(b/305849394): Make mMenuController multi-user aware.
- final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
+ final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
? mMenuControllerNew.isShowing()
: mMenuController.getSwitchingDialogLocked() != null;
if (switcherMenuShowing) {
@@ -2614,7 +2643,8 @@
|| (visibility & InputMethodService.IME_INVISIBLE) != 0) {
return false;
}
- if (mWindowManagerInternal.isHardKeyboardAvailable() && !Flags.imeSwitcherRevamp()) {
+ if (mWindowManagerInternal.isHardKeyboardAvailable()
+ && !mNewInputMethodSwitcherMenuEnabled) {
// When physical keyboard is attached, we show the ime switcher (or notification if
// NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
// exists in the IME switcher dialog. Might be OK to remove this condition once
@@ -2625,7 +2655,7 @@
}
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- if (Flags.imeSwitcherRevamp()) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
// The IME switcher button should be shown when the current IME specified a
// language settings activity.
final var curImi = settings.getMethodMap().get(settings.getSelectedInputMethod());
@@ -2744,20 +2774,18 @@
if (targetWindow != null) {
mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow);
}
- mLastImeTargetWindow = targetWindow;
+ mVisibilityStateComputer.setLastImeTargetWindow(targetWindow);
}
}
- private void updateImeWindowStatus(boolean disableImeIcon) {
- synchronized (ImfLock.class) {
- // TODO(b/350386877): Propagate userId from the caller.
- final int userId = mCurrentUserId;
- if (disableImeIcon) {
- final var bindingController = getInputMethodBindingController(userId);
- updateSystemUiLocked(0, bindingController.getBackDisposition(), userId);
- } else {
- updateSystemUiLocked(userId);
- }
+ @GuardedBy("ImfLock.class")
+ private void updateImeWindowStatusLocked(boolean disableImeIcon, int displayId) {
+ final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
+ if (disableImeIcon) {
+ final var bindingController = getInputMethodBindingController(userId);
+ updateSystemUiLocked(0, bindingController.getBackDisposition(), userId);
+ } else {
+ updateSystemUiLocked(userId);
}
}
@@ -2803,7 +2831,7 @@
}
final var curId = bindingController.getCurId();
// TODO(b/305849394): Make mMenuController multi-user aware.
- final boolean switcherMenuShowing = Flags.imeSwitcherRevamp()
+ final boolean switcherMenuShowing = mNewInputMethodSwitcherMenuEnabled
? mMenuControllerNew.isShowing()
: mMenuController.getSwitchingDialogLocked() != null;
if (switcherMenuShowing
@@ -2825,65 +2853,11 @@
@GuardedBy("ImfLock.class")
void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
updateInputMethodsFromSettingsLocked(enabledMayChange, userId);
- if (!Flags.imeSwitcherRevamp()) {
+ if (!mNewInputMethodSwitcherMenuEnabled) {
mMenuController.updateKeyboardFromSettingsLocked();
}
}
- /**
- * This initialization logic is used when and only when {@link #mConcurrentMultiUserModeEnabled}
- * is set to {@code true}.
- *
- * <p>There remain several yet-to-be-implemented features. For the canonical and desired
- * behaviors always refer to single-user code paths such as
- * {@link #updateInputMethodsFromSettingsLocked(boolean, int)}.</p>
- *
- * <p>Here are examples of missing features.</p>
- * <ul>
- * <li>Profiles are not supported.</li>
- * <li>
- * {@link PackageManager#COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED} is not updated.
- * </li>
- * <li>{@link InputMethodBindingController#getDeviceIdToShowIme()} is ignored.</li>
- * <li>and so on.</li>
- * </ul>
- */
- @GuardedBy("ImfLock.class")
- void initializeVisibleBackgroundUserLocked(@UserIdInt int userId) {
- final var settings = InputMethodSettingsRepository.get(userId);
-
- // Until we figure out what makes most sense, we enable all the pre-installed IMEs in
- // concurrent multi-user IME mode.
- String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
- for (var imi : settings.getMethodList()) {
- if (!imi.isSystem()) {
- continue;
- }
- enabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(enabledImeIdsStr, imi.getId());
- }
- if (!TextUtils.equals(settings.getEnabledInputMethodsStr(), enabledImeIdsStr)) {
- settings.putEnabledInputMethodsStr(enabledImeIdsStr);
- }
-
- // Also update the currently-selected IME.
- String id = settings.getSelectedInputMethod();
- if (TextUtils.isEmpty(id)) {
- final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
- settings.getEnabledInputMethodList());
- if (imi != null) {
- id = imi.getId();
- settings.putSelectedInputMethod(id);
- }
- }
- final var userData = getUserData(userId);
- final var bindingController = userData.mBindingController;
- bindingController.setSelectedMethodId(id);
-
- // Also re-initialize controllers.
- userData.mSwitchingController.resetCircularListLocked(mContext, settings);
- userData.mHardwareKeyboardShortcutController.update(settings);
- }
-
@GuardedBy("ImfLock.class")
void updateInputMethodsFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
@@ -3062,52 +3036,75 @@
}
}
+ @GuardedBy("ImfLock.class")
+ private void sendResultReceiverFailureLocked(@Nullable ResultReceiver resultReceiver) {
+ final boolean isInputShown = mVisibilityStateComputer.isInputShown();
+ resultReceiver.send(isInputShown
+ ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+ : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
+ }
+
@Override
public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
int lastClickToolType, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#showSoftInput", mDumper);
synchronized (ImfLock.class) {
- final int userId = resolveImeUserIdLocked(callingUserId);
- if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken,
- userId)) {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- return false;
+ final boolean result = showSoftInputLocked(client, windowToken, statsToken, flags,
+ lastClickToolType, resultReceiver, reason);
+ // When ZeroJankProxy is enabled, the app has already received "true" as the return
+ // value, and expect "resultReceiver" to be notified later. See b/327751155.
+ if (!result && Flags.useZeroJankProxy()) {
+ sendResultReceiverFailureLocked(resultReceiver);
}
- 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 (userData.mImeBindingState != null
- && userData.mImeBindingState.mFocusedWindowClient != null
- && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(true, statsToken);
- if (resultReceiver != null) {
- resultReceiver.send(
- wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
- : InputMethodManager.RESULT_SHOWN, null);
- }
- return true;
+ return result; // ignored when ZeroJankProxy is enabled.
+ }
+ }
+
+ @GuardedBy("ImfLock.class")
+ private boolean showSoftInputLocked(IInputMethodClient client, IBinder windowToken,
+ @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+ int lastClickToolType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason) {
+ final int uid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(uid);
+ final int userId = resolveImeUserIdLocked(callingUserId);
+ if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken,
+ userId)) {
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ return false;
+ }
+ final var userData = getUserData(userId);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
+ if (Flags.refactorInsetsController()) {
+ boolean wasVisible = mVisibilityStateComputer.isInputShown();
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
+ userData.mImeBindingState.mFocusedWindowClient.mClient
+ .setImeVisibility(true, statsToken);
+ if (resultReceiver != null) {
+ resultReceiver.send(
+ wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
+ : InputMethodManager.RESULT_SHOWN, null);
}
- return false;
- } else {
- return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType,
- resultReceiver, reason, userId);
+ return true;
}
- } finally {
- Binder.restoreCallingIdentity(ident);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ return false;
+ } else {
+ return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType,
+ resultReceiver, reason, userId);
}
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -3117,8 +3114,7 @@
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#showSoftInput", mDumper);
synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from windowToken
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
@@ -3138,8 +3134,7 @@
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#hideSoftInput", mDumper);
synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from windowToken
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
@@ -3401,14 +3396,14 @@
Objects.requireNonNull(windowToken, "windowToken must not be null");
synchronized (ImfLock.class) {
Boolean windowPerceptible = mFocusedWindowPerceptible.get(windowToken);
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
final var userData = getUserData(userId);
if (userData.mImeBindingState.mFocusedWindow != windowToken
|| (windowPerceptible != null && windowPerceptible == perceptible)) {
return;
}
mFocusedWindowPerceptible.put(windowToken, windowPerceptible);
- updateSystemUiLocked(mCurrentUserId);
+ updateSystemUiLocked(userId);
}
});
}
@@ -3502,50 +3497,64 @@
public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- final int uid = Binder.getCallingUid();
- final int callingUserId = UserHandle.getUserId(uid);
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#hideSoftInput", mDumper);
synchronized (ImfLock.class) {
- final int userId = resolveImeUserIdLocked(callingUserId);
- if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken, userId)) {
- if (isInputShownLocked()) {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
- } else {
- ImeTracker.forLogging().onCancelled(statsToken,
- ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ final boolean result = hideSoftInputLocked(client, windowToken, statsToken, flags,
+ resultReceiver, reason);
+ // When ZeroJankProxy is enabled, the app has already received "true" as the return
+ // value, and expect "resultReceiver" to be notified later. See b/327751155.
+ if (!result && Flags.useZeroJankProxy()) {
+ sendResultReceiverFailureLocked(resultReceiver);
+ }
+ return result; // ignored when ZeroJankProxy is enabled.
+ }
+ }
+
+ @GuardedBy("ImfLock.class")
+ private boolean hideSoftInputLocked(IInputMethodClient client, IBinder windowToken,
+ @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ final int uid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(uid);
+ final int userId = resolveImeUserIdLocked(callingUserId);
+ if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken, userId)) {
+ if (mVisibilityStateComputer.isInputShown()) {
+ ImeTracker.forLogging().onFailed(
+ statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ } else {
+ ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ }
+ return false;
+ }
+ final var userData = getUserData(userId);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
+ if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
+ if (Flags.refactorInsetsController()) {
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
+ boolean wasVisible = mVisibilityStateComputer.isInputShown();
+ // TODO add windowToken to interface
+ userData.mImeBindingState.mFocusedWindowClient.mClient
+ .setImeVisibility(false, statsToken);
+ if (resultReceiver != null) {
+ resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN
+ : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
+ }
+ return true;
}
return false;
+ } else {
+ return InputMethodManagerService.this.hideCurrentInputLocked(
+ windowToken, statsToken, flags, resultReceiver, reason, userId);
}
- 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 (userData.mImeBindingState != null
- && userData.mImeBindingState.mFocusedWindowClient != null
- && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
- boolean wasVisible = isInputShownLocked();
- // TODO add windowToken to interface
- userData.mImeBindingState.mFocusedWindowClient.mClient
- .setImeVisibility(false, statsToken);
- if (resultReceiver != null) {
- resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN
- : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
- }
- return true;
- }
- return false;
- } else {
- return InputMethodManagerService.this.hideCurrentInputLocked(windowToken,
- statsToken, flags, resultReceiver, reason, userId);
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -3590,7 +3599,7 @@
// TODO(b/246309664): Clean up IMMS#mImeWindowVis
IInputMethodInvoker curMethod = bindingController.getCurMethod();
final boolean shouldHideSoftInput = curMethod != null
- && (isInputShownLocked()
+ && (mVisibilityStateComputer.isInputShown()
|| (bindingController.getImeWindowVis() & InputMethodService.IME_ACTIVE) != 0);
mVisibilityStateComputer.requestImeVisibility(windowToken, false);
@@ -3962,6 +3971,10 @@
@Override
public void showInputMethodPickerFromClient(IInputMethodClient client,
int auxiliarySubtypeMode) {
+ if (mConcurrentMultiUserModeEnabled) {
+ Slog.w(TAG, "showInputMethodPickerFromClient is not enabled on automotive");
+ return;
+ }
final int callingUserId = UserHandle.getCallingUserId();
synchronized (ImfLock.class) {
if (!canShowInputMethodPickerLocked(client)) {
@@ -3995,7 +4008,7 @@
@IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
public boolean isInputMethodPickerShownForTest() {
synchronized (ImfLock.class) {
- return Flags.imeSwitcherRevamp()
+ return mNewInputMethodSwitcherMenuEnabled
? mMenuControllerNew.isShowing()
: mMenuController.isisInputMethodPickerShownForTestLocked();
}
@@ -4074,7 +4087,7 @@
@Override
public void onImeSwitchButtonClickFromSystem(int displayId) {
synchronized (ImfLock.class) {
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
final var userData = getUserData(userId);
final var bindingController = userData.mBindingController;
final var curToken = bindingController.getCurToken();
@@ -4414,7 +4427,7 @@
@IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
@Override
- public void removeImeSurface() {
+ public void removeImeSurface(int displayId) {
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
@@ -4683,8 +4696,8 @@
proto.write(CUR_SEQ, bindingController.getSequenceNumber());
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(LAST_IME_TARGET_WINDOW_NAME, mWindowManagerInternal.getWindowName(
+ mVisibilityStateComputer.getLastImeTargetWindow()));
proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(
userData.mImeBindingState.mFocusedWindowSoftInputMode));
if (userData.mCurEditorInfo != null) {
@@ -4701,7 +4714,7 @@
proto.write(IS_INTERACTIVE, mIsInteractive);
proto.write(BACK_DISPOSITION, bindingController.getBackDisposition());
proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis());
- if (!Flags.imeSwitcherRevamp()) {
+ if (!mNewInputMethodSwitcherMenuEnabled) {
proto.write(SHOW_IME_WITH_HARD_KEYBOARD,
mMenuController.getShowImeWithHardKeyboard());
}
@@ -4858,8 +4871,8 @@
.setImeVisibility(false, statsToken);
}
} else {
- hideCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
- null /* resultReceiver */, reason, userId);
+ hideCurrentInputLocked(mVisibilityStateComputer.getLastImeTargetWindow(),
+ statsToken, flags, null /* resultReceiver */, reason, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4897,9 +4910,9 @@
.setImeVisibility(true, statsToken);
}
} else {
- showCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
- MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason,
- userId);
+ showCurrentInputLocked(mVisibilityStateComputer.getLastImeTargetWindow(),
+ statsToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN,
+ null /* resultReceiver */, reason, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4916,14 +4929,12 @@
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(), userId);
- }
+ @GuardedBy("ImfLock.class")
+ void onApplyImeVisibilityFromComputerLocked(IBinder windowToken,
+ @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result) {
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
+ mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(),
+ result.getReason(), userId);
}
@GuardedBy("ImfLock.class")
@@ -4993,7 +5004,7 @@
// implemented so that auxiliary subtypes will be excluded when the soft
// keyboard is invisible.
synchronized (ImfLock.class) {
- showAuxSubtypes = isInputShownLocked();
+ showAuxSubtypes = mVisibilityStateComputer.isInputShown();
}
break;
case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
@@ -5028,7 +5039,7 @@
return false;
}
- if (Flags.imeSwitcherRevamp()) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
if (DEBUG) {
Slog.v(TAG, "Show IME switcher menu,"
+ " showAuxSubtypes=" + showAuxSubtypes
@@ -5097,8 +5108,7 @@
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 int userId = resolveImeUserIdFromWindowLocked(windowToken);
final var userData = getUserData(userId);
try {
if (windowToken == userData.mImeBindingState.mFocusedWindow
@@ -5111,10 +5121,6 @@
}
return true;
}
- case MSG_UPDATE_IME_WINDOW_STATUS: {
- updateImeWindowStatus(msg.arg1 == 1);
- return true;
- }
// ---------------------------------------------------------
@@ -5124,18 +5130,13 @@
// --------------------------------------------------------------
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
- if (!Flags.imeSwitcherRevamp()) {
+ if (!mNewInputMethodSwitcherMenuEnabled) {
mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1);
}
synchronized (ImfLock.class) {
sendOnNavButtonFlagsChangedToAllImesLocked();
}
return true;
- case MSG_SYSTEM_UNLOCK_USER: {
- final int userId = msg.arg1;
- onUnlockUser(userId);
- return true;
- }
case MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED: {
final int userId = msg.arg1;
final List<InputMethodInfo> imes = (List<InputMethodInfo>) msg.obj;
@@ -5907,8 +5908,7 @@
@Override
public void reportImeControl(@Nullable IBinder windowToken) {
synchronized (ImfLock.class) {
- // TODO(b/305849394): Need to infer userId or get userId from callers.
- final int userId = mCurrentUserId;
+ final int userId = resolveImeUserIdFromWindowLocked(windowToken);
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
@@ -5923,14 +5923,14 @@
@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 int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
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 != userData.mImeBindingState.mFocusedWindow) {
- if (Flags.imeSwitcherRevamp()) {
+ if (mVisibilityStateComputer.getLastImeTargetWindow()
+ != userData.mImeBindingState.mFocusedWindow) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
final var bindingController = getInputMethodBindingController(userId);
mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId);
} else {
@@ -5949,8 +5949,11 @@
@ImfLockFree
@Override
public void updateImeWindowStatus(boolean disableImeIcon, int displayId) {
- mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
- .sendToTarget();
+ mHandler.post(() -> {
+ synchronized (ImfLock.class) {
+ updateImeWindowStatusLocked(disableImeIcon, displayId);
+ }
+ });
}
@Override
@@ -6048,8 +6051,8 @@
public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
IBinder targetWindowToken) {
synchronized (ImfLock.class) {
- // TODO(b/305849394): Infer userId from displayId
- switchKeyboardLayoutLocked(direction, getUserData(mCurrentUserId));
+ final int userId = resolveImeUserIdFromDisplayIdLocked(displayId);
+ switchKeyboardLayoutLocked(direction, getUserData(userId));
}
}
}
@@ -6283,7 +6286,7 @@
};
mUserDataRepository.forAllUserData(userDataDump);
- if (Flags.imeSwitcherRevamp()) {
+ if (mNewInputMethodSwitcherMenuEnabled) {
p.println(" menuControllerNew:");
mMenuControllerNew.dump(p, " ");
} else {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
index 1b84036..4f5af63 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -19,15 +19,13 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
final class InputMethodSettingsRepository {
- // TODO(b/352594784): Should we user other lock primitives?
- @GuardedBy("sPerUserMap")
+ private static final Object sMutationLock = new Object();
+
@NonNull
- private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+ private static volatile ImmutableSparseArray<InputMethodSettings> sPerUserMap =
+ ImmutableSparseArray.empty();
/**
* Not intended to be instantiated.
@@ -38,10 +36,7 @@
@NonNull
@AnyThread
static InputMethodSettings get(@UserIdInt int userId) {
- final InputMethodSettings obj;
- synchronized (sPerUserMap) {
- obj = sPerUserMap.get(userId);
- }
+ final InputMethodSettings obj = sPerUserMap.get(userId);
if (obj != null) {
return obj;
}
@@ -50,15 +45,15 @@
@AnyThread
static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
- synchronized (sPerUserMap) {
- sPerUserMap.put(userId, obj);
+ synchronized (sMutationLock) {
+ sPerUserMap = sPerUserMap.cloneWithPutOrSelf(userId, obj);
}
}
@AnyThread
static void remove(@UserIdInt int userId) {
- synchronized (sPerUserMap) {
- sPerUserMap.remove(userId);
+ synchronized (sMutationLock) {
+ sPerUserMap = sPerUserMap.cloneWithRemoveOrSelf(userId);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
index e7cff20..b3500be 100644
--- a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
@@ -24,7 +24,6 @@
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
@@ -38,6 +37,13 @@
* to the persistent value when the user storage is unlocked.</p>
*/
final class SecureSettingsWrapper {
+
+ private static final Object sMutationLock = new Object();
+
+ @NonNull
+ private static volatile ImmutableSparseArray<ReaderWriter> sUserMap =
+ ImmutableSparseArray.empty();
+
@Nullable
private static volatile ContentResolver sContentResolver = null;
@@ -61,8 +67,8 @@
*/
@AnyThread
static void endTestMode() {
- synchronized (sUserMap) {
- sUserMap.clear();
+ synchronized (sMutationLock) {
+ sUserMap = ImmutableSparseArray.empty();
}
sTestMode = false;
}
@@ -243,10 +249,6 @@
}
}
- @GuardedBy("sUserMap")
- @NonNull
- private static final SparseArray<ReaderWriter> sUserMap = new SparseArray<>();
-
private static final ReaderWriter NOOP = new ReaderWriter() {
@Override
public void putString(String key, String str) {
@@ -282,15 +284,15 @@
private static ReaderWriter putOrGet(@UserIdInt int userId,
@NonNull ReaderWriter readerWriter) {
final boolean isUnlockedUserImpl = readerWriter instanceof UnlockedUserImpl;
- synchronized (sUserMap) {
+ synchronized (sMutationLock) {
final ReaderWriter current = sUserMap.get(userId);
if (current == null) {
- sUserMap.put(userId, readerWriter);
+ sUserMap = sUserMap.cloneWithPutOrSelf(userId, readerWriter);
return readerWriter;
}
// Upgrading from CopyOnWriteImpl to DirectImpl is allowed.
if (current instanceof LockedUserImpl && isUnlockedUserImpl) {
- sUserMap.put(userId, readerWriter);
+ sUserMap = sUserMap.cloneWithPutOrSelf(userId, readerWriter);
return readerWriter;
}
return current;
@@ -300,11 +302,9 @@
@NonNull
@AnyThread
private static ReaderWriter get(@UserIdInt int userId) {
- synchronized (sUserMap) {
- final ReaderWriter readerWriter = sUserMap.get(userId);
- if (readerWriter != null) {
- return readerWriter;
- }
+ final ReaderWriter readerWriter = sUserMap.get(userId);
+ if (readerWriter != null) {
+ return readerWriter;
}
if (sTestMode) {
return putOrGet(userId, new FakeReaderWriterImpl());
@@ -363,8 +363,8 @@
*/
@AnyThread
static void onUserRemoved(@UserIdInt int userId) {
- synchronized (sUserMap) {
- sUserMap.remove(userId);
+ synchronized (sMutationLock) {
+ sUserMap = sUserMap.cloneWithRemoveOrSelf(userId);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 6f831cc..e3524b1 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -19,51 +19,39 @@
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
-import android.util.SparseArray;
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.IntFunction;
final class UserDataRepository {
- private final ReentrantReadWriteLock mUserDataLock = new ReentrantReadWriteLock();
+ private final Object mMutationLock = new Object();
- @GuardedBy("mUserDataLock")
- private final SparseArray<UserData> mUserData = new SparseArray<>();
+ @NonNull
+ private volatile ImmutableSparseArray<UserData> mUserData = ImmutableSparseArray.empty();
private final IntFunction<InputMethodBindingController> mBindingControllerFactory;
@AnyThread
@NonNull
UserData getOrCreate(@UserIdInt int userId) {
- mUserDataLock.writeLock().lock();
- try {
- UserData userData = mUserData.get(userId);
- if (userData == null) {
- userData = new UserData(userId, mBindingControllerFactory.apply(userId));
- mUserData.put(userId, userData);
- }
+ // Do optimistic read first for optimization.
+ final var userData = mUserData.get(userId);
+ if (userData != null) {
return userData;
- } finally {
- mUserDataLock.writeLock().unlock();
+ }
+ // Note that the below line can be called concurrently. Here we assume that
+ // instantiating UserData for the same user multiple times would have no side effect.
+ final var newUserData = new UserData(userId, mBindingControllerFactory.apply(userId));
+ synchronized (mMutationLock) {
+ mUserData = mUserData.cloneWithPutOrSelf(userId, newUserData);
+ return newUserData;
}
}
@AnyThread
void forAllUserData(Consumer<UserData> consumer) {
- final SparseArray<UserData> copiedArray;
- mUserDataLock.readLock().lock();
- try {
- copiedArray = mUserData.clone();
- } finally {
- mUserDataLock.readLock().unlock();
- }
- for (int i = 0; i < copiedArray.size(); i++) {
- consumer.accept(copiedArray.valueAt(i));
- }
+ mUserData.forEach(consumer);
}
UserDataRepository(
@@ -73,11 +61,8 @@
@AnyThread
void remove(@UserIdInt int userId) {
- mUserDataLock.writeLock().lock();
- try {
- mUserData.remove(userId);
- } finally {
- mUserDataLock.writeLock().unlock();
+ synchronized (mMutationLock) {
+ mUserData = mUserData.cloneWithRemoveOrSelf(userId);
}
}
}
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 770e12d..f603ff3 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -86,8 +86,6 @@
interface Callback extends IInputMethodManagerImpl.Callback {
@GuardedBy("ImfLock.class")
ClientState getClientStateLocked(IInputMethodClient client);
- @GuardedBy("ImfLock.class")
- boolean isInputShownLocked();
}
private final Callback mInner;
@@ -178,19 +176,8 @@
@Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
@MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
- offload(
- () -> {
- if (!mInner.showSoftInput(
- client,
- windowToken,
- statsToken,
- flags,
- lastClickToolType,
- resultReceiver,
- reason)) {
- sendResultReceiverFailure(resultReceiver);
- }
- });
+ offload(() -> mInner.showSoftInput(
+ client, windowToken, statsToken, flags, lastClickToolType, resultReceiver, reason));
return true;
}
@@ -198,30 +185,11 @@
public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
@Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- offload(
- () -> {
- if (!mInner.hideSoftInput(
- client, windowToken, statsToken, flags, resultReceiver, reason)) {
- sendResultReceiverFailure(resultReceiver);
- }
- });
+ offload(() -> mInner.hideSoftInput(
+ client, windowToken, statsToken, flags, resultReceiver, reason));
return true;
}
- private void sendResultReceiverFailure(@Nullable ResultReceiver resultReceiver) {
- if (resultReceiver == null) {
- return;
- }
- final boolean isInputShown;
- synchronized (ImfLock.class) {
- isInputShown = mInner.isInputShownLocked();
- }
- resultReceiver.send(isInputShown
- ? InputMethodManager.RESULT_UNCHANGED_SHOWN
- : InputMethodManager.RESULT_UNCHANGED_HIDDEN,
- null);
- }
-
@Override
@IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
public void hideSoftInputFromServerForTest() {
@@ -332,8 +300,8 @@
@IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
@Override
- public void removeImeSurface() {
- mInner.removeImeSurface();
+ public void removeImeSurface(int displayId) {
+ mInner.removeImeSurface(displayId);
}
@Override
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index b12a917e..95d8bb9 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -2811,8 +2811,9 @@
private final class H extends Handler {
private static final int MSG_DISPATCH = 1;
private static final int MSG_METRICS = 2;
- private static final int MSG_RINGER_AUDIO = 5;
private static final int MSG_APPLY_EFFECTS = 6;
+ private static final int MSG_AUDIO_APPLIED_TO_RINGER = 7;
+ private static final int MSG_AUDIO_NOT_APPLIED_TO_RINGER = 8;
private static final long METRICS_PERIOD_MS = 6 * 60 * 60 * 1000;
@@ -2831,8 +2832,13 @@
}
private void postUpdateRingerAndAudio(boolean shouldApplyToRinger) {
- removeMessages(MSG_RINGER_AUDIO);
- sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger));
+ if (shouldApplyToRinger) {
+ removeMessages(MSG_AUDIO_APPLIED_TO_RINGER);
+ sendEmptyMessage(MSG_AUDIO_APPLIED_TO_RINGER);
+ } else {
+ removeMessages(MSG_AUDIO_NOT_APPLIED_TO_RINGER);
+ sendEmptyMessage(MSG_AUDIO_NOT_APPLIED_TO_RINGER);
+ }
}
private void postApplyDeviceEffects(@ConfigChangeOrigin int origin) {
@@ -2849,9 +2855,11 @@
case MSG_METRICS:
mMetrics.emit();
break;
- case MSG_RINGER_AUDIO:
- boolean shouldApplyToRinger = (boolean) msg.obj;
- updateRingerAndAudio(shouldApplyToRinger);
+ case MSG_AUDIO_APPLIED_TO_RINGER:
+ updateRingerAndAudio(/* shouldApplyToRinger= */ true);
+ break;
+ case MSG_AUDIO_NOT_APPLIED_TO_RINGER:
+ updateRingerAndAudio(/* shouldApplyToRinger= */ false);
break;
case MSG_APPLY_EFFECTS:
@ConfigChangeOrigin int origin = msg.arg1;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 8d3f07e..a17c48d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3264,6 +3264,7 @@
/**
* Tries to restore the disabled system package after an update has been deleted.
*/
+ @GuardedBy("mPm.mInstallLock")
public void restoreDisabledSystemPackageLIF(DeletePackageAction action,
@NonNull int[] allUserHandles, boolean writeSettings) throws SystemDeleteException {
final PackageSetting deletedPs = action.mDeletingPs;
@@ -3282,10 +3283,21 @@
}
// Install the system package
if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
- try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
+ try {
final int[] origUsers = outInfo == null ? null : outInfo.mOrigUsers;
- installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
- origUsers, writeSettings);
+ try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
+ installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
+ origUsers, writeSettings);
+ }
+ if (origUsers != null) {
+ mPm.commitPackageStateMutation(null, mutator -> {
+ for (int userId : origUsers) {
+ mutator.forPackage(disabledPs.getPackageName())
+ .userState(userId)
+ .setOverlayPaths(deletedPs.getOverlayPaths(userId));
+ }
+ });
+ }
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": "
+ e.getMessage());
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 6b7b702..5e45b4c 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -869,18 +869,25 @@
private int createDraftSession(String packageName, String installerPackage,
String callerPackageName,
IntentSender statusReceiver, int userId) throws IOException {
+ Computer snapshot = mPm.snapshotComputer();
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
sessionParams.setAppLabel(
mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
- sessionParams.setAppIcon(
- getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
+ // The draft session's app icon is based on the current launcher's icon overlay appops mode
+ String launcherPackageName = getCurrentLauncherPackageName(userId);
+ int launcherUid = launcherPackageName != null
+ ? snapshot.getPackageUid(launcherPackageName, 0, userId)
+ : Process.SYSTEM_UID;
+ sessionParams.setAppIcon(getArchivedAppIcon(packageName, UserHandle.of(userId),
+ isOverlayEnabled(launcherUid,
+ launcherPackageName == null ? callerPackageName : launcherPackageName)));
// To make sure SessionInfo::isUnarchival returns true for draft sessions,
// INSTALL_UNARCHIVE is also set.
sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
- int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
+ int installerUid = snapshot.getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
sessionParams,
@@ -926,12 +933,27 @@
/**
* Returns the icon of an archived app. This is the icon of the main activity of the app.
*
- * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
- * launcher activities, only one of the icons is returned arbitrarily.
+ * <p> In the rare case the app had multiple launcher activities, only one of the icons is
+ * returned arbitrarily.
+ *
+ * <p> By default, the icon will be overlay'd with a cloud icon on top. A launcher app can
+ * disable the cloud overlay via the
+ * {@link LauncherApps.ArchiveCompatibilityParams#setEnableIconOverlay(boolean)} API.
+ * The default launcher's cloud overlay mode determines the cloud overlay status returned by
+ * any other callers. That is, if the current launcher has the cloud overlay disabled, any other
+ * app that fetches the app icon will also get an icon that has the cloud overlay disabled.
+ * This is to prevent style mismatch caused by icons that are fetched by different callers.
*/
@Nullable
public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
String callingPackageName) {
+ return getArchivedAppIcon(packageName, user,
+ isOverlayEnabled(Binder.getCallingUid(), callingPackageName));
+ }
+
+ @Nullable
+ private Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user,
+ boolean isOverlayEnabled) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(user);
@@ -955,14 +977,19 @@
// In the rare case the archived app defined more than two launcher activities, we choose
// the first one arbitrarily.
Bitmap icon = decodeIcon(archiveState.getActivityInfos().get(0));
- if (icon != null && getAppOpsManager().checkOp(
- AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, callingPackageName)
- == MODE_ALLOWED) {
+
+ if (icon != null && isOverlayEnabled) {
icon = includeCloudOverlay(icon);
}
return icon;
}
+ private boolean isOverlayEnabled(int callingUid, String packageName) {
+ return getAppOpsManager().checkOp(
+ AppOpsManager.OP_ARCHIVE_ICON_OVERLAY, callingUid, packageName)
+ == MODE_ALLOWED;
+ }
+
/**
* This method first checks the ArchiveState for the provided userId and then tries to fallback
* to other users if the current user is not archived.
diff --git a/services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java
new file mode 100644
index 0000000..a42929f
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/AmbientDisplayPowerStatsProcessor.java
@@ -0,0 +1,75 @@
+/*
+ * 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.power.stats;
+
+import android.os.BatteryConsumer;
+import android.os.PersistableBundle;
+
+import com.android.internal.os.PowerStats;
+
+public class AmbientDisplayPowerStatsProcessor extends PowerStatsProcessor {
+ private final PowerStatsLayout mStatsLayout;
+ private final PowerStats.Descriptor mDescriptor;
+ private final long[] mTmpDeviceStats;
+ private PowerStats.Descriptor mScreenPowerStatsDescriptor;
+ private ScreenPowerStatsLayout mScreenPowerStatsLayout;
+ private long[] mTmpScreenStats;
+
+ public AmbientDisplayPowerStatsProcessor() {
+ mStatsLayout = new PowerStatsLayout();
+ mStatsLayout.addDeviceSectionPowerEstimate();
+ PersistableBundle extras = new PersistableBundle();
+ mStatsLayout.toExtras(extras);
+ mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+ mStatsLayout.getDeviceStatsArrayLength(), null, 0, 0, extras);
+ mTmpDeviceStats = new long[mDescriptor.statsArrayLength];
+ }
+
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ stats.setPowerStatsDescriptor(mDescriptor);
+
+ PowerComponentAggregatedPowerStats screenStats =
+ stats.getAggregatedPowerStats().getPowerComponentStats(
+ BatteryConsumer.POWER_COMPONENT_SCREEN);
+ if (screenStats == null) {
+ return;
+ }
+
+ if (mScreenPowerStatsDescriptor == null) {
+ mScreenPowerStatsDescriptor = screenStats.getPowerStatsDescriptor();
+ if (mScreenPowerStatsDescriptor == null) {
+ return;
+ }
+
+ mScreenPowerStatsLayout = new ScreenPowerStatsLayout(mScreenPowerStatsDescriptor);
+ mTmpScreenStats = new long[mScreenPowerStatsDescriptor.statsArrayLength];
+ }
+
+ MultiStateStats.States[] deviceStateConfig = screenStats.getConfig().getDeviceStateConfig();
+
+ // Ambient display power estimates have already been calculated by the screen power stats
+ // processor. All that remains to be done is copy the estimates over.
+ MultiStateStats.States.forEachTrackedStateCombination(deviceStateConfig,
+ states -> {
+ screenStats.getDeviceStats(mTmpScreenStats, states);
+ double power =
+ mScreenPowerStatsLayout.getScreenDozePowerEstimate(mTmpScreenStats);
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStats, power);
+ stats.setDeviceStats(states, mTmpDeviceStats);
+ });
+ }
+}
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 4052a64..c4b37c69 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -296,6 +296,7 @@
private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
private int[] mCpuPowerBracketMap;
private final CpuPowerStatsCollector mCpuPowerStatsCollector;
+ private final ScreenPowerStatsCollector mScreenPowerStatsCollector;
private final MobileRadioPowerStatsCollector mMobileRadioPowerStatsCollector;
private final WifiPowerStatsCollector mWifiPowerStatsCollector;
private final BluetoothPowerStatsCollector mBluetoothPowerStatsCollector;
@@ -303,6 +304,54 @@
private final GnssPowerStatsCollector mGnssPowerStatsCollector;
private final CustomEnergyConsumerPowerStatsCollector mCustomEnergyConsumerPowerStatsCollector;
private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray();
+ private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever =
+ new ScreenPowerStatsCollector.ScreenUsageTimeRetriever() {
+
+ @Override
+ public long getScreenOnTimeMs(int display) {
+ synchronized (BatteryStatsImpl.this) {
+ return getDisplayScreenOnTime(display,
+ mClock.elapsedRealtime() * 1000) / 1000;
+ }
+ }
+
+ @Override
+ public long getBrightnessLevelTimeMs(int display, int brightnessLevel) {
+ synchronized (BatteryStatsImpl.this) {
+ return getDisplayScreenBrightnessTime(display, brightnessLevel,
+ mClock.elapsedRealtime() * 1000) / 1000;
+ }
+ }
+
+ @Override
+ public long getScreenDozeTimeMs(int display) {
+ synchronized (BatteryStatsImpl.this) {
+ return getDisplayScreenDozeTime(display,
+ mClock.elapsedRealtime() * 1000) / 1000;
+ }
+ }
+
+ @Override
+ public void retrieveTopActivityTimes(Callback callback) {
+ synchronized (BatteryStatsImpl.this) {
+ long elapsedTimeUs = mClock.elapsedRealtime() * 1000;
+ for (int i = mUidStats.size() - 1; i >= 0; i--) {
+ Uid uid = mUidStats.valueAt(i);
+ long topStateTime = uid.getProcessStateTime(Uid.PROCESS_STATE_TOP,
+ elapsedTimeUs, STATS_SINCE_CHARGED) / 1000;
+ Timer timer = uid.getForegroundActivityTimer();
+ if (timer == null) {
+ callback.onUidTopActivityTime(uid.mUid, topStateTime);
+ } else {
+ long topActivityTime = timer.getTotalTimeLocked(elapsedTimeUs,
+ STATS_SINCE_CHARGED) / 1000;
+ callback.onUidTopActivityTime(uid.mUid, Math.min(topStateTime,
+ topActivityTime));
+ }
+ }
+ }
+ }
+ };
private final WifiPowerStatsCollector.WifiStatsRetriever mWifiStatsRetriever =
new WifiPowerStatsCollector.WifiStatsRetriever() {
@Override
@@ -1966,8 +2015,9 @@
}
private class PowerStatsCollectorInjector implements CpuPowerStatsCollector.Injector,
- MobileRadioPowerStatsCollector.Injector, WifiPowerStatsCollector.Injector,
- BluetoothPowerStatsCollector.Injector, EnergyConsumerPowerStatsCollector.Injector {
+ ScreenPowerStatsCollector.Injector, MobileRadioPowerStatsCollector.Injector,
+ WifiPowerStatsCollector.Injector, BluetoothPowerStatsCollector.Injector,
+ EnergyConsumerPowerStatsCollector.Injector {
private PackageManager mPackageManager;
private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
private NetworkStatsManager mNetworkStatsManager;
@@ -2039,6 +2089,16 @@
}
@Override
+ public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() {
+ return mScreenUsageTimeRetriever;
+ }
+
+ @Override
+ public int getDisplayCount() {
+ return BatteryStatsImpl.this.getDisplayCount();
+ }
+
+ @Override
public Supplier<NetworkStats> getMobileNetworkStatsSupplier() {
return () -> readMobileNetworkStatsLocked(mNetworkStatsManager);
}
@@ -5736,13 +5796,17 @@
maybeUpdateOverallScreenBrightness(overallBin, elapsedRealtimeMs, uptimeMs);
if (shouldScheduleSync) {
- final int numDisplays = mPerDisplayBatteryStats.length;
- final int[] displayStates = new int[numDisplays];
- for (int i = 0; i < numDisplays; i++) {
- displayStates[i] = mPerDisplayBatteryStats[i].screenState;
+ if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN)) {
+ mScreenPowerStatsCollector.schedule();
+ } else {
+ final int numDisplays = mPerDisplayBatteryStats.length;
+ final int[] displayStates = new int[numDisplays];
+ for (int i = 0; i < numDisplays; i++) {
+ displayStates[i] = mPerDisplayBatteryStats[i].screenState;
+ }
+ mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag,
+ batteryRunning, batteryScreenOffRunning, state, displayStates);
}
- mExternalSync.scheduleSyncDueToScreenStateChange(externalUpdateFlag,
- batteryRunning, batteryScreenOffRunning, state, displayStates);
}
}
@@ -11290,6 +11354,9 @@
mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector);
mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
+ mScreenPowerStatsCollector = new ScreenPowerStatsCollector(mPowerStatsCollectorInjector);
+ mScreenPowerStatsCollector.addConsumer(this::recordPowerStats);
+
mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector(
mPowerStatsCollectorInjector, this::onMobileRadioPowerStatsRetrieved);
mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats);
@@ -14750,6 +14817,10 @@
mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CPU));
mCpuPowerStatsCollector.schedule();
+ mScreenPowerStatsCollector.setEnabled(
+ mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN));
+ mScreenPowerStatsCollector.schedule();
+
mMobileRadioPowerStatsCollector.setEnabled(
mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO));
mMobileRadioPowerStatsCollector.schedule();
@@ -14786,6 +14857,8 @@
switch (powerComponent) {
case BatteryConsumer.POWER_COMPONENT_CPU:
return mCpuPowerStatsCollector;
+ case BatteryConsumer.POWER_COMPONENT_SCREEN:
+ return mScreenPowerStatsCollector;
case BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO:
return mMobileRadioPowerStatsCollector;
case BatteryConsumer.POWER_COMPONENT_WIFI:
@@ -16329,6 +16402,7 @@
*/
public void schedulePowerStatsSampleCollection() {
mCpuPowerStatsCollector.forceSchedule();
+ mScreenPowerStatsCollector.forceSchedule();
mMobileRadioPowerStatsCollector.forceSchedule();
mWifiPowerStatsCollector.forceSchedule();
mBluetoothPowerStatsCollector.forceSchedule();
@@ -16351,6 +16425,7 @@
*/
public void dumpStatsSample(PrintWriter pw) {
mCpuPowerStatsCollector.collectAndDump(pw);
+ mScreenPowerStatsCollector.collectAndDump(pw);
mMobileRadioPowerStatsCollector.collectAndDump(pw);
mWifiPowerStatsCollector.collectAndDump(pw);
mBluetoothPowerStatsCollector.collectAndDump(pw);
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index ac68966..a5e4cf5 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -110,8 +110,13 @@
if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_VIDEO)) {
mPowerCalculators.add(new VideoPowerCalculator(mPowerProfile));
}
- mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
- mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
+ if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_SCREEN)) {
+ mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
+ }
+ if (!mPowerStatsExporterEnabled.get(
+ BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY)) {
+ mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
+ }
mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_ANY)) {
mPowerCalculators.add(new CustomEnergyConsumerPowerCalculator(mPowerProfile));
diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
index 8384b2b..6820197 100644
--- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
+++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java
@@ -163,15 +163,14 @@
}
}
- void setDeviceStats(@AggregatedPowerStatsConfig.TrackedState int[] states, long[] values) {
+ void setDeviceStats(int[] states, long[] values) {
if (mDeviceStats == null) {
createDeviceStats(0);
}
mDeviceStats.setStats(states, values);
}
- void setUidStats(int uid, @AggregatedPowerStatsConfig.TrackedState int[] states,
- long[] values) {
+ void setUidStats(int uid, int[] states, long[] values) {
UidStats uidStats = getUidStats(uid);
uidStats.stats.setStats(states, values);
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
index dfc8daa..7d7b3c2 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java
@@ -220,8 +220,7 @@
}
@Nullable
- public DeviceStateEstimation getDeviceStateEstimate(
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ public DeviceStateEstimation getDeviceStateEstimate(int[] stateValues) {
String label = concatLabels(mConfig.getDeviceStateConfig(), stateValues);
for (int i = 0; i < deviceStateEstimations.size(); i++) {
DeviceStateEstimation deviceStateEstimation = this.deviceStateEstimations.get(i);
@@ -233,8 +232,7 @@
}
public CombinedDeviceStateEstimate getCombinedDeviceStateEstimate(
- MultiStateStats.States[] deviceStates,
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ MultiStateStats.States[] deviceStates, int[] stateValues) {
String label = concatLabels(deviceStates, stateValues);
for (int i = 0; i < combinedDeviceStateEstimations.size(); i++) {
CombinedDeviceStateEstimate cdse = combinedDeviceStateEstimations.get(i);
@@ -275,12 +273,10 @@
protected static class DeviceStateEstimation {
public final String id;
- @AggregatedPowerStatsConfig.TrackedState
public final int[] stateValues;
public Object intermediates;
- public DeviceStateEstimation(MultiStateStats.States[] config,
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ public DeviceStateEstimation(MultiStateStats.States[] config, int[] stateValues) {
id = concatLabels(config, stateValues);
this.stateValues = stateValues;
}
@@ -288,11 +284,12 @@
protected static class CombinedDeviceStateEstimate {
public final String id;
+ public final int[] stateValues;
public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>();
public Object intermediates;
- public CombinedDeviceStateEstimate(MultiStateStats.States[] config,
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ public CombinedDeviceStateEstimate(MultiStateStats.States[] config, int[] stateValues) {
+ this.stateValues = Arrays.copyOf(stateValues, stateValues.length);
id = concatLabels(config, stateValues);
}
}
@@ -310,19 +307,16 @@
}
protected static class UidStateProportionalEstimate {
- @AggregatedPowerStatsConfig.TrackedState
public final int[] stateValues;
public Object intermediates;
- protected UidStateProportionalEstimate(
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ protected UidStateProportionalEstimate(int[] stateValues) {
this.stateValues = stateValues;
}
}
@NonNull
- private static String concatLabels(MultiStateStats.States[] config,
- @AggregatedPowerStatsConfig.TrackedState int[] stateValues) {
+ private static String concatLabels(MultiStateStats.States[] config, int[] stateValues) {
List<String> labels = new ArrayList<>();
for (int state = 0; state < config.length; state++) {
if (config[state] != null && config[state].isTracked()) {
@@ -334,7 +328,6 @@
return labels.toString();
}
- @AggregatedPowerStatsConfig.TrackedState
private static int[][] getAllTrackedStateCombinations(MultiStateStats.States[] states) {
List<int[]> combinations = new ArrayList<>();
MultiStateStats.States.forEachTrackedStateCombination(states, stateValues -> {
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java
new file mode 100644
index 0000000..291f289
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsCollector.java
@@ -0,0 +1,227 @@
+/*
+ * 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.power.stats;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.util.Slog;
+import android.util.SparseLongArray;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerStats;
+
+import java.util.Arrays;
+import java.util.function.IntSupplier;
+
+public class ScreenPowerStatsCollector extends PowerStatsCollector {
+ private static final String TAG = "ScreenPowerStatsCollector";
+
+ interface ScreenUsageTimeRetriever {
+ interface Callback {
+ void onUidTopActivityTime(int uid, long topActivityTimeMs);
+ }
+
+ void retrieveTopActivityTimes(Callback callback);
+
+ long getScreenOnTimeMs(int display);
+ long getBrightnessLevelTimeMs(int display, int brightnessLevel);
+ long getScreenDozeTimeMs(int display);
+ }
+
+ interface Injector {
+ Handler getHandler();
+ Clock getClock();
+ PowerStatsUidResolver getUidResolver();
+ long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
+ ConsumedEnergyRetriever getConsumedEnergyRetriever();
+ IntSupplier getVoltageSupplier();
+ ScreenUsageTimeRetriever getScreenUsageTimeRetriever();
+ int getDisplayCount();
+ }
+
+ private static final long ENERGY_UNSPECIFIED = -1;
+
+ private final Injector mInjector;
+ private boolean mIsInitialized;
+ private ScreenPowerStatsLayout mLayout;
+ private int mDisplayCount;
+ private PowerStats mPowerStats;
+ private ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ private IntSupplier mVoltageSupplier;
+ private ScreenUsageTimeRetriever mScreenUsageTimeRetriever;
+ private int[] mEnergyConsumerIds = new int[0];
+ private long[] mLastConsumedEnergyUws;
+ private int mLastVoltageMv;
+ private boolean mFirstSample = true;
+ private long[] mLastScreenOnTime;
+ private long[][] mLastBrightnessLevelTime;
+ private long[] mLastDozeTime;
+ private final SparseLongArray mLastTopActivityTime = new SparseLongArray();
+ private long mLastCollectionTime;
+
+ ScreenPowerStatsCollector(Injector injector) {
+ super(injector.getHandler(),
+ injector.getPowerStatsCollectionThrottlePeriod(
+ BatteryConsumer.powerComponentIdToString(
+ BatteryConsumer.POWER_COMPONENT_SCREEN)),
+ injector.getUidResolver(), injector.getClock());
+ mInjector = injector;
+ }
+
+ private boolean ensureInitialized() {
+ if (mIsInitialized) {
+ return true;
+ }
+
+ if (!isEnabled()) {
+ return false;
+ }
+
+ mDisplayCount = mInjector.getDisplayCount();
+ mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
+ mVoltageSupplier = mInjector.getVoltageSupplier();
+ mScreenUsageTimeRetriever = mInjector.getScreenUsageTimeRetriever();
+ mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(
+ EnergyConsumerType.DISPLAY);
+ mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length];
+ Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED);
+
+ mLayout = new ScreenPowerStatsLayout();
+ mLayout.addDeviceScreenUsageDurationSection(mInjector.getDisplayCount());
+ mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length);
+ mLayout.addDeviceSectionUsageDuration();
+ mLayout.addDeviceSectionPowerEstimate();
+ mLayout.addUidTopActivitiyDuration();
+ mLayout.addUidSectionPowerEstimate();
+
+ PersistableBundle extras = new PersistableBundle();
+ mLayout.toExtras(extras);
+ PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor(
+ BatteryConsumer.POWER_COMPONENT_SCREEN, mLayout.getDeviceStatsArrayLength(),
+ null, 0, mLayout.getUidStatsArrayLength(),
+ extras);
+
+ mLastScreenOnTime = new long[mDisplayCount];
+ mLastBrightnessLevelTime = new long[mDisplayCount][BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS];
+ mLastDozeTime = new long[mDisplayCount];
+
+ mPowerStats = new PowerStats(powerStatsDescriptor);
+
+ mIsInitialized = true;
+ return true;
+ }
+
+ @Override
+ protected PowerStats collectStats() {
+ if (!ensureInitialized()) {
+ return null;
+ }
+
+ if (mEnergyConsumerIds.length != 0) {
+ collectEnergyConsumers();
+ }
+
+ for (int display = 0; display < mDisplayCount; display++) {
+ long screenOnTimeMs = mScreenUsageTimeRetriever.getScreenOnTimeMs(display);
+ if (!mFirstSample) {
+ mLayout.setScreenOnDuration(mPowerStats.stats, display,
+ screenOnTimeMs - mLastScreenOnTime[display]);
+ }
+ mLastScreenOnTime[display] = screenOnTimeMs;
+
+ for (int level = 0; level < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; level++) {
+ long brightnessLevelTimeMs =
+ mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(display, level);
+ if (!mFirstSample) {
+ mLayout.setBrightnessLevelDuration(mPowerStats.stats, display, level,
+ brightnessLevelTimeMs - mLastBrightnessLevelTime[display][level]);
+ }
+ mLastBrightnessLevelTime[display][level] = brightnessLevelTimeMs;
+ }
+ long screenDozeTimeMs = mScreenUsageTimeRetriever.getScreenDozeTimeMs(display);
+ if (!mFirstSample) {
+ mLayout.setScreenDozeDuration(mPowerStats.stats, display,
+ screenDozeTimeMs - mLastDozeTime[display]);
+ }
+ mLastDozeTime[display] = screenDozeTimeMs;
+ }
+
+ mPowerStats.uidStats.clear();
+
+ mScreenUsageTimeRetriever.retrieveTopActivityTimes((uid, topActivityTimeMs) -> {
+ long topActivityDuration = topActivityTimeMs - mLastTopActivityTime.get(uid);
+ if (topActivityDuration == 0) {
+ return;
+ }
+ mLastTopActivityTime.put(uid, topActivityTimeMs);
+
+ int mappedUid = mUidResolver.mapUid(uid);
+ long[] uidStats = mPowerStats.uidStats.get(mappedUid);
+ if (uidStats == null) {
+ uidStats = new long[mLayout.getUidStatsArrayLength()];
+ mPowerStats.uidStats.put(mappedUid, uidStats);
+ }
+
+ mLayout.setUidTopActivityDuration(uidStats,
+ mLayout.getUidTopActivityDuration(uidStats) + topActivityDuration);
+ });
+
+ long elapsedRealtime = mClock.elapsedRealtime();
+ mPowerStats.durationMs = elapsedRealtime - mLastCollectionTime;
+ mLastCollectionTime = elapsedRealtime;
+
+ mFirstSample = false;
+
+ return mPowerStats;
+ }
+
+ private void collectEnergyConsumers() {
+ int voltageMv = mVoltageSupplier.getAsInt();
+ if (voltageMv <= 0) {
+ Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
+ + " mV) when querying energy consumers");
+ return;
+ }
+
+ int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
+ mLastVoltageMv = voltageMv;
+
+ long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds);
+ if (energyUws == null) {
+ return;
+ }
+
+ for (int i = energyUws.length - 1; i >= 0; i--) {
+ long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED
+ ? energyUws[i] - mLastConsumedEnergyUws[i] : 0;
+ if (energyDelta < 0) {
+ // Likely, restart of powerstats HAL
+ energyDelta = 0;
+ }
+ mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage));
+ mLastConsumedEnergyUws[i] = energyUws[i];
+ }
+ }
+
+ @Override
+ protected void onUidRemoved(int uid) {
+ mLastTopActivityTime.delete(uid);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java
new file mode 100644
index 0000000..f134aa8
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsLayout.java
@@ -0,0 +1,171 @@
+/*
+ * 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.power.stats;
+
+import android.annotation.NonNull;
+import android.os.BatteryStats;
+import android.os.PersistableBundle;
+
+import com.android.internal.os.PowerStats;
+
+/**
+ * Captures the positions and lengths of sections of the stats array, such as time-in-state,
+ * power usage estimates etc.
+ */
+public class ScreenPowerStatsLayout extends PowerStatsLayout {
+ private static final String EXTRA_DEVICE_SCREEN_COUNT = "dsc";
+ private static final String EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION = "dsd";
+ private static final String EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS = "dbd";
+ private static final String EXTRA_DEVICE_DOZE_DURATION_POSITION = "ddd";
+ private static final String EXTRA_DEVICE_DOZE_POWER_POSITION = "ddp";
+ private static final String EXTRA_UID_FOREGROUND_DURATION = "uf";
+
+ private int mDisplayCount;
+ private int mDeviceScreenOnDurationPosition;
+ private int[] mDeviceBrightnessDurationPositions;
+ private int mDeviceScreenDozeDurationPosition;
+ private int mDeviceScreenDozePowerPosition;
+ private int mUidTopActivityTimePosition;
+
+ ScreenPowerStatsLayout() {
+ }
+
+ ScreenPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) {
+ super(descriptor);
+ }
+
+ void addDeviceScreenUsageDurationSection(int displayCount) {
+ mDisplayCount = displayCount;
+ mDeviceScreenOnDurationPosition = addDeviceSection(displayCount, "on");
+ mDeviceBrightnessDurationPositions = new int[BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS];
+ for (int level = 0; level < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; level++) {
+ mDeviceBrightnessDurationPositions[level] =
+ addDeviceSection(displayCount, BatteryStats.SCREEN_BRIGHTNESS_NAMES[level]);
+ }
+ mDeviceScreenDozeDurationPosition = addDeviceSection(displayCount, "doze");
+ }
+
+ @Override
+ public void addDeviceSectionPowerEstimate() {
+ super.addDeviceSectionPowerEstimate();
+ // Used by AmbientDisplayPowerStatsProcessor
+ mDeviceScreenDozePowerPosition = addDeviceSection(1, "doze-power", FLAG_HIDDEN);
+ }
+
+ public int getDisplayCount() {
+ return mDisplayCount;
+ }
+
+ /**
+ * Stores screen-on time for the specified display.
+ */
+ public void setScreenOnDuration(long[] stats, int display, long durationMs) {
+ stats[mDeviceScreenOnDurationPosition + display] = durationMs;
+ }
+
+ /**
+ * Returns screen-on time for the specified display.
+ */
+ public long getScreenOnDuration(long[] stats, int display) {
+ return stats[mDeviceScreenOnDurationPosition + display];
+ }
+
+ /**
+ * Stores time at the specified brightness level for the specified display.
+ */
+ public void setBrightnessLevelDuration(long[] stats, int display, int brightnessLevel,
+ long durationMs) {
+ stats[mDeviceBrightnessDurationPositions[brightnessLevel] + display] = durationMs;
+ }
+
+ /**
+ * Returns time at the specified brightness level for the specified display.
+ */
+ public long getBrightnessLevelDuration(long[] stats, int display, int brightnessLevel) {
+ return stats[mDeviceBrightnessDurationPositions[brightnessLevel] + display];
+ }
+
+ /**
+ * Stores time in the doze (ambient) state for the specified display.
+ */
+ public void setScreenDozeDuration(long[] stats, int display, long durationMs) {
+ stats[mDeviceScreenDozeDurationPosition + display] = durationMs;
+ }
+
+ /**
+ * Retrieves time in the doze (ambient) state for the specified display.
+ */
+ public long getScreenDozeDuration(long[] stats, int display) {
+ return stats[mDeviceScreenDozeDurationPosition + display];
+ }
+
+ /**
+ * Stores estimated power in the doze (ambient) state.
+ */
+ public void setScreenDozePowerEstimate(long[] stats, double power) {
+ stats[mDeviceScreenDozePowerPosition] = (long) (power * MILLI_TO_NANO_MULTIPLIER);
+ }
+
+ /**
+ * Retrieves estimated power in the doze (ambient) state.
+ */
+ public double getScreenDozePowerEstimate(long[] stats) {
+ return stats[mDeviceScreenDozePowerPosition] / MILLI_TO_NANO_MULTIPLIER;
+ }
+
+ void addUidTopActivitiyDuration() {
+ mUidTopActivityTimePosition = addUidSection(1, "top");
+ }
+
+ /**
+ * Stores time the UID spent in the TOP state.
+ */
+ public void setUidTopActivityDuration(long[] stats, long durationMs) {
+ stats[mUidTopActivityTimePosition] = durationMs;
+ }
+
+ /**
+ * Returns time the UID spent in the TOP state.
+ */
+ public long getUidTopActivityDuration(long[] stats) {
+ return stats[mUidTopActivityTimePosition];
+ }
+
+ @Override
+ public void toExtras(PersistableBundle extras) {
+ super.toExtras(extras);
+ extras.putInt(EXTRA_DEVICE_SCREEN_COUNT, mDisplayCount);
+ extras.putInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION, mDeviceScreenOnDurationPosition);
+ extras.putIntArray(EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS,
+ mDeviceBrightnessDurationPositions);
+ extras.putInt(EXTRA_DEVICE_DOZE_DURATION_POSITION, mDeviceScreenDozeDurationPosition);
+ extras.putInt(EXTRA_DEVICE_DOZE_POWER_POSITION, mDeviceScreenDozePowerPosition);
+ extras.putInt(EXTRA_UID_FOREGROUND_DURATION, mUidTopActivityTimePosition);
+ }
+
+ @Override
+ public void fromExtras(PersistableBundle extras) {
+ super.fromExtras(extras);
+ mDisplayCount = extras.getInt(EXTRA_DEVICE_SCREEN_COUNT, 1);
+ mDeviceScreenOnDurationPosition = extras.getInt(EXTRA_DEVICE_SCREEN_ON_DURATION_POSITION);
+ mDeviceBrightnessDurationPositions = extras.getIntArray(
+ EXTRA_DEVICE_BRIGHTNESS_DURATION_POSITIONS);
+ mDeviceScreenDozeDurationPosition = extras.getInt(EXTRA_DEVICE_DOZE_DURATION_POSITION);
+ mDeviceScreenDozePowerPosition = extras.getInt(EXTRA_DEVICE_DOZE_POWER_POSITION);
+ mUidTopActivityTimePosition = extras.getInt(EXTRA_UID_FOREGROUND_DURATION);
+ }
+}
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
new file mode 100644
index 0000000..e203e4a
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerStatsProcessor.java
@@ -0,0 +1,238 @@
+/*
+ * 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.power.stats;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
+
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_AMBIENT;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL;
+import static com.android.internal.os.PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import android.os.BatteryStats;
+import android.util.Slog;
+
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ScreenPowerStatsProcessor extends PowerStatsProcessor {
+ private static final String TAG = "ScreenPowerStatsProcessor";
+ private final int mDisplayCount;
+ private final UsageBasedPowerEstimator[] mScreenOnPowerEstimators;
+ private final UsageBasedPowerEstimator[] mScreenDozePowerEstimators;
+ private final UsageBasedPowerEstimator[][] mScreenBrightnessLevelPowerEstimators;
+ private PowerStats.Descriptor mLastUsedDescriptor;
+ private ScreenPowerStatsLayout mStatsLayout;
+ private PowerEstimationPlan mPlan;
+ private long[] mTmpDeviceStatsArray;
+ private long[] mTmpUidStatsArray;
+
+ private static class Intermediates {
+ public double power;
+ }
+
+ public ScreenPowerStatsProcessor(PowerProfile powerProfile) {
+ mDisplayCount = powerProfile.getNumDisplays();
+ mScreenOnPowerEstimators = new UsageBasedPowerEstimator[mDisplayCount];
+ mScreenDozePowerEstimators = new UsageBasedPowerEstimator[mDisplayCount];
+ mScreenBrightnessLevelPowerEstimators = new UsageBasedPowerEstimator[mDisplayCount][];
+ for (int display = 0; display < mDisplayCount; display++) {
+ mScreenOnPowerEstimators[display] = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, display));
+
+ double averagePowerFullBrightness = powerProfile.getAveragePowerForOrdinal(
+ POWER_GROUP_DISPLAY_SCREEN_FULL, display);
+ mScreenBrightnessLevelPowerEstimators[display] =
+ new UsageBasedPowerEstimator[BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS];
+ for (int bin = 0; bin < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ // For example, if the number of bins is 3, the corresponding averages
+ // are calculated as 0.5 * full, 1.5 * full, 2.5 * full
+ final double binPowerMah = averagePowerFullBrightness * (bin + 0.5)
+ / BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS;
+ mScreenBrightnessLevelPowerEstimators[display][bin] =
+ new UsageBasedPowerEstimator(binPowerMah);
+ }
+
+ mScreenDozePowerEstimators[display] = new UsageBasedPowerEstimator(
+ powerProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, display));
+ }
+ }
+
+ private boolean unpackPowerStatsDescriptor(PowerStats.Descriptor descriptor) {
+ if (descriptor == null) {
+ return false;
+ }
+
+ if (descriptor.equals(mLastUsedDescriptor)) {
+ return true;
+ }
+
+ mLastUsedDescriptor = descriptor;
+ mStatsLayout = new ScreenPowerStatsLayout(descriptor);
+ if (mStatsLayout.getDisplayCount() != mDisplayCount) {
+ Slog.e(TAG, "Incompatible number of displays: " + mStatsLayout.getDisplayCount()
+ + ", expected: " + mDisplayCount);
+ return false;
+ }
+
+ mTmpDeviceStatsArray = new long[descriptor.statsArrayLength];
+ mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength];
+ return true;
+ }
+
+ @Override
+ void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
+ if (!unpackPowerStatsDescriptor(stats.getPowerStatsDescriptor())) {
+ return;
+ }
+
+ if (mPlan == null) {
+ mPlan = new PowerEstimationPlan(stats.getConfig());
+ }
+
+ computeDevicePowerEstimates(stats);
+ combineDeviceStateEstimates();
+
+ List<Integer> uids = new ArrayList<>();
+ stats.collectUids(uids);
+
+ if (!uids.isEmpty()) {
+ computeUidPowerEstimates(stats, uids);
+ }
+ }
+
+ private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
+ for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+ DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i);
+ if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) {
+ continue;
+ }
+
+ if (estimation.stateValues[STATE_SCREEN] == SCREEN_STATE_ON) {
+ double power;
+ if (mStatsLayout.getEnergyConsumerCount() > 0) {
+ power = uCtoMah(mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, 0));
+ } else {
+ power = 0;
+ for (int display = 0; display < mStatsLayout.getDisplayCount(); display++) {
+ power += computeDisplayPower(mTmpDeviceStatsArray, display);
+ }
+ }
+ mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power);
+ Intermediates intermediates = new Intermediates();
+ intermediates.power = power;
+ estimation.intermediates = intermediates;
+ } else {
+ double power = 0;
+ if (mStatsLayout.getEnergyConsumerCount() > 0) {
+ power = uCtoMah(mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, 0));
+ } else {
+ for (int display = 0; display < mStatsLayout.getDisplayCount(); display++) {
+ power += mScreenDozePowerEstimators[display].calculatePower(
+ mStatsLayout.getScreenDozeDuration(mTmpDeviceStatsArray, display));
+ }
+ }
+ mStatsLayout.setScreenDozePowerEstimate(mTmpDeviceStatsArray, power);
+ }
+
+ stats.setDeviceStats(estimation.stateValues, mTmpDeviceStatsArray);
+ }
+ }
+
+ private double computeDisplayPower(long[] stats, int display) {
+ double power = mScreenOnPowerEstimators[display]
+ .calculatePower(mStatsLayout.getScreenOnDuration(stats, display));
+ for (int bin = 0; bin < BatteryStats.NUM_SCREEN_BRIGHTNESS_BINS; bin++) {
+ power += mScreenBrightnessLevelPowerEstimators[display][bin]
+ .calculatePower(mStatsLayout.getBrightnessLevelDuration(stats, display, bin));
+ }
+ return power;
+ }
+
+ /**
+ * Combine power estimates before distributing them proportionally to UIDs.
+ */
+ private void combineDeviceStateEstimates() {
+ for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) {
+ CombinedDeviceStateEstimate cdse = mPlan.combinedDeviceStateEstimations.get(i);
+ List<DeviceStateEstimation> deviceStateEstimations = cdse.deviceStateEstimations;
+ double power = 0;
+ for (int j = deviceStateEstimations.size() - 1; j >= 0; j--) {
+ DeviceStateEstimation dse = deviceStateEstimations.get(j);
+ Intermediates intermediates = (Intermediates) dse.intermediates;
+ if (intermediates != null) {
+ power += intermediates.power;
+ }
+ }
+ if (power != 0) {
+ Intermediates cdseIntermediates = new Intermediates();
+ cdseIntermediates.power = power;
+ cdse.intermediates = cdseIntermediates;
+ }
+ }
+ }
+
+ private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats,
+ List<Integer> uids) {
+ int[] uidStateValues = new int[stats.getConfig().getUidStateConfig().length];
+ uidStateValues[STATE_SCREEN] = SCREEN_STATE_ON;
+ uidStateValues[STATE_PROCESS_STATE] = PROCESS_STATE_ANY;
+
+ for (int i = mPlan.uidStateEstimates.size() - 1; i >= 0; i--) {
+ UidStateEstimate uidStateEstimate = mPlan.uidStateEstimates.get(i);
+ Intermediates intermediates =
+ (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates;
+ int[] deviceStateValues = uidStateEstimate.combinedDeviceStateEstimate
+ .stateValues;
+ if (deviceStateValues[STATE_SCREEN] != SCREEN_STATE_ON
+ || intermediates == null) {
+ continue;
+ }
+
+ uidStateValues[STATE_POWER] = deviceStateValues[STATE_POWER];
+
+ long totalTopActivityDuration = 0;
+ for (int j = uids.size() - 1; j >= 0; j--) {
+ int uid = uids.get(j);
+ if (stats.getUidStats(mTmpUidStatsArray, uid, uidStateValues)) {
+ totalTopActivityDuration +=
+ mStatsLayout.getUidTopActivityDuration(mTmpUidStatsArray);
+ }
+ }
+
+ if (totalTopActivityDuration == 0) {
+ return;
+ }
+
+ for (int j = uids.size() - 1; j >= 0; j--) {
+ int uid = uids.get(j);
+ if (stats.getUidStats(mTmpUidStatsArray, uid, uidStateValues)) {
+ long duration = mStatsLayout.getUidTopActivityDuration(mTmpUidStatsArray);
+ double power = intermediates.power * duration / totalTopActivityDuration;
+ mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power);
+ stats.setUidStats(uid, uidStateValues, mTmpUidStatsArray);
+ }
+ }
+ }
+ }
+}
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 bca81f52..c21f783 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -64,6 +64,7 @@
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
+import static com.android.server.stats.Flags.applyNetworkStatsPollRateLimit;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -298,6 +299,13 @@
*/
private static final long NETSTATS_UID_DEFAULT_BUCKET_DURATION_MS = HOURS.toMillis(2);
+ /**
+ * Polling NetworkStats is a heavy operation and it should be done sparingly. Atom pulls may
+ * happen in bursts, but these should be infrequent. The poll rate limit ensures that data is
+ * sufficiently fresh (i.e. not stale) while reducing system load during atom pull bursts.
+ */
+ private static final long NETSTATS_POLL_RATE_LIMIT_MS = 15000;
+
private static final int CPU_TIME_PER_THREAD_FREQ_MAX_NUM_FREQUENCIES = 8;
private static final int OP_FLAGS_PULLED = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED;
private static final String COMMON_PERMISSION_PREFIX = "android.permission.";
@@ -415,6 +423,9 @@
@GuardedBy("mDataBytesTransferLock")
private final ArrayList<NetworkStatsExt> mNetworkStatsBaselines = new ArrayList<>();
+ @GuardedBy("mDataBytesTransferLock")
+ private long mLastNetworkStatsPollTime = -NETSTATS_POLL_RATE_LIMIT_MS;
+
// Listener for monitoring subscriptions changed event.
private StatsSubscriptionsListener mStatsSubscriptionsListener;
// List that stores SubInfo of subscriptions that ever appeared since boot.
@@ -1063,24 +1074,26 @@
// Initialize NetworkStats baselines.
synchronized (mDataBytesTransferLock) {
mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.WIFI_BYTES_TRANSFER));
+ collectNetworkStatsSnapshotForAtomLocked(
+ FrameworkStatsLog.WIFI_BYTES_TRANSFER));
mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(
+ collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG));
mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
- mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+ collectNetworkStatsSnapshotForAtomLocked(
+ FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
+ mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG));
- mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+ mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED));
mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(
+ collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER));
mNetworkStatsBaselines.addAll(
- collectNetworkStatsSnapshotForAtom(
+ collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER));
if (canQueryTypeProxy) {
- mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+ mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtomLocked(
FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG));
}
}
@@ -1243,12 +1256,14 @@
);
}
+ @GuardedBy("mDataBytesTransferLock")
@NonNull
- private List<NetworkStatsExt> collectNetworkStatsSnapshotForAtom(int atomTag) {
+ private List<NetworkStatsExt> collectNetworkStatsSnapshotForAtomLocked(int atomTag) {
List<NetworkStatsExt> ret = new ArrayList<>();
switch (atomTag) {
case FrameworkStatsLog.WIFI_BYTES_TRANSFER: {
- final NetworkStats stats = getUidNetworkStatsSnapshotForTransport(TRANSPORT_WIFI);
+ final NetworkStats stats = getUidNetworkStatsSnapshotForTransportLocked(
+ TRANSPORT_WIFI);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/false));
@@ -1256,7 +1271,8 @@
break;
}
case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
- final NetworkStats stats = getUidNetworkStatsSnapshotForTransport(TRANSPORT_WIFI);
+ final NetworkStats stats = getUidNetworkStatsSnapshotForTransportLocked(
+ TRANSPORT_WIFI);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
new int[]{TRANSPORT_WIFI}, /*slicedByFgbg=*/true));
@@ -1265,7 +1281,7 @@
}
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: {
final NetworkStats stats =
- getUidNetworkStatsSnapshotForTransport(TRANSPORT_CELLULAR);
+ getUidNetworkStatsSnapshotForTransportLocked(TRANSPORT_CELLULAR);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUid(stats),
new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
@@ -1274,7 +1290,7 @@
}
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
final NetworkStats stats =
- getUidNetworkStatsSnapshotForTransport(TRANSPORT_CELLULAR);
+ getUidNetworkStatsSnapshotForTransportLocked(TRANSPORT_CELLULAR);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true));
@@ -1282,7 +1298,7 @@
break;
}
case FrameworkStatsLog.PROXY_BYTES_TRANSFER_BY_FG_BG: {
- final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(
+ final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_PROXY).build(), /*includeTags=*/false);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
@@ -1294,9 +1310,9 @@
break;
}
case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: {
- final NetworkStats wifiStats = getUidNetworkStatsSnapshotForTemplate(
+ final NetworkStats wifiStats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_WIFI).build(), /*includeTags=*/true);
- final NetworkStats cellularStats = getUidNetworkStatsSnapshotForTemplate(
+ final NetworkStats cellularStats = getUidNetworkStatsSnapshotForTemplateLocked(
new NetworkTemplate.Builder(MATCH_MOBILE)
.setMeteredness(METERED_YES).build(), /*includeTags=*/true);
if (wifiStats != null && cellularStats != null) {
@@ -1311,12 +1327,12 @@
}
case FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER: {
for (final SubInfo subInfo : mHistoricalSubs) {
- ret.addAll(getDataUsageBytesTransferSnapshotForSub(subInfo));
+ ret.addAll(getDataUsageBytesTransferSnapshotForSubLocked(subInfo));
}
break;
}
case FrameworkStatsLog.OEM_MANAGED_BYTES_TRANSFER: {
- ret.addAll(getDataUsageBytesTransferSnapshotForOemManaged());
+ ret.addAll(getDataUsageBytesTransferSnapshotForOemManagedLocked());
break;
}
default:
@@ -1325,8 +1341,9 @@
return ret;
}
+ @GuardedBy("mDataBytesTransferLock")
private int pullDataBytesTransferLocked(int atomTag, @NonNull List<StatsEvent> pulledData) {
- final List<NetworkStatsExt> current = collectNetworkStatsSnapshotForAtom(atomTag);
+ final List<NetworkStatsExt> current = collectNetworkStatsSnapshotForAtomLocked(atomTag);
if (current == null) {
Slog.e(TAG, "current snapshot is null for " + atomTag + ", return.");
@@ -1459,8 +1476,9 @@
}
}
+ @GuardedBy("mDataBytesTransferLock")
@NonNull
- private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForOemManaged() {
+ private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForOemManagedLocked() {
final List<Pair<Integer, Integer>> matchRulesAndTransports = List.of(
new Pair(MATCH_ETHERNET, TRANSPORT_ETHERNET),
new Pair(MATCH_MOBILE, TRANSPORT_CELLULAR),
@@ -1479,7 +1497,8 @@
// Thus, specifying networks through their identifiers are not needed.
final NetworkTemplate template = new NetworkTemplate.Builder(matchRule)
.setOemManaged(oemManaged).build();
- final NetworkStats stats = getUidNetworkStatsSnapshotForTemplate(template, false);
+ final NetworkStats stats = getUidNetworkStatsSnapshotForTemplateLocked(
+ template, false);
final Integer transport = ruleAndTransport.second;
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
@@ -1496,8 +1515,9 @@
/**
* Create a snapshot of NetworkStats for a given transport.
*/
+ @GuardedBy("mDataBytesTransferLock")
@Nullable
- private NetworkStats getUidNetworkStatsSnapshotForTransport(int transport) {
+ private NetworkStats getUidNetworkStatsSnapshotForTransportLocked(int transport) {
NetworkTemplate template = null;
switch (transport) {
case TRANSPORT_CELLULAR:
@@ -1510,7 +1530,7 @@
default:
Log.wtf(TAG, "Unexpected transport.");
}
- return getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
+ return getUidNetworkStatsSnapshotForTemplateLocked(template, /*includeTags=*/false);
}
/**
@@ -1534,8 +1554,9 @@
* Note that this should be only used to calculate diff since the snapshot might contains
* some traffic before boot.
*/
+ @GuardedBy("mDataBytesTransferLock")
@Nullable
- private NetworkStats getUidNetworkStatsSnapshotForTemplate(
+ private NetworkStats getUidNetworkStatsSnapshotForTemplateLocked(
@NonNull NetworkTemplate template, boolean includeTags) {
final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
final long currentTimeInMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
@@ -1547,13 +1568,19 @@
final long startTime = currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration;
final long endTime = currentTimeInMillis + bucketDuration;
- // TODO (b/156313635): This is short-term hack to allow perfd gets updated networkStats
- // history when query in every second in order to show realtime statistics. However,
- // this is not a good long-term solution since NetworkStatsService will make frequent
- // I/O and also block main thread when polling.
- // Consider making perfd queries NetworkStatsService directly.
- if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
- getNetworkStatsManager().forceUpdate();
+ // NetworkStatsManager#forceUpdate updates stats for all networks
+ if (applyNetworkStatsPollRateLimit()) {
+ // The new way: rate-limit force-polling for all NetworkStats queries
+ if (elapsedMillisSinceBoot - mLastNetworkStatsPollTime >= NETSTATS_POLL_RATE_LIMIT_MS) {
+ mLastNetworkStatsPollTime = elapsedMillisSinceBoot;
+ getNetworkStatsManager().forceUpdate();
+ }
+ } else {
+ // The old way: force-poll only on WiFi queries. Data for other queries can be stale
+ // if there was no recent poll beforehand (e.g. for WiFi or scheduled poll)
+ if (template.getMatchRule() == MATCH_WIFI && template.getSubscriberIds().isEmpty()) {
+ getNetworkStatsManager().forceUpdate();
+ }
}
final android.app.usage.NetworkStats queryNonTaggedStats =
@@ -1572,8 +1599,9 @@
return nonTaggedStats.add(taggedStats);
}
+ @GuardedBy("mDataBytesTransferLock")
@NonNull
- private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForSub(
+ private List<NetworkStatsExt> getDataUsageBytesTransferSnapshotForSubLocked(
@NonNull SubInfo subInfo) {
final List<NetworkStatsExt> ret = new ArrayList<>();
for (final int ratType : getAllCollapsedRatTypes()) {
@@ -1583,7 +1611,7 @@
.setRatType(ratType)
.setMeteredness(METERED_YES).build();
final NetworkStats stats =
- getUidNetworkStatsSnapshotForTemplate(template, /*includeTags=*/false);
+ getUidNetworkStatsSnapshotForTemplateLocked(template, /*includeTags=*/false);
if (stats != null) {
ret.add(new NetworkStatsExt(sliceNetworkStatsByFgbg(stats),
new int[]{TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true,
@@ -5273,6 +5301,13 @@
@Override
public void onSubscriptionsChanged() {
+ synchronized (mDataBytesTransferLock) {
+ onSubscriptionsChangedLocked();
+ }
+ }
+
+ @GuardedBy("mDataBytesTransferLock")
+ private void onSubscriptionsChangedLocked() {
final List<SubscriptionInfo> currentSubs = mSm.getCompleteActiveSubscriptionInfoList();
for (final SubscriptionInfo sub : currentSubs) {
final SubInfo match = CollectionUtils.find(mHistoricalSubs,
@@ -5295,12 +5330,11 @@
subscriberId, sub.isOpportunistic());
Slog.i(TAG, "subId " + subId + " added into historical sub list");
- synchronized (mDataBytesTransferLock) {
- mHistoricalSubs.add(subInfo);
- // Since getting snapshot when pulling will also include data before boot,
- // query stats as baseline to prevent double count is needed.
- mNetworkStatsBaselines.addAll(getDataUsageBytesTransferSnapshotForSub(subInfo));
- }
+ mHistoricalSubs.add(subInfo);
+ // Since getting snapshot when pulling will also include data before boot,
+ // query stats as baseline to prevent double count is needed.
+ mNetworkStatsBaselines.addAll(
+ getDataUsageBytesTransferSnapshotForSubLocked(subInfo));
}
}
}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index 6faa273..f360837 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -8,3 +8,11 @@
bug: "309512867"
is_fixed_read_only: true
}
+
+flag {
+ name: "apply_network_stats_poll_rate_limit"
+ namespace: "statsd"
+ description: "Apply a rate limit for polling network stats when pulling relevant atoms"
+ bug: "352495181"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
index aa2b74e..58c3ba5 100644
--- a/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
+++ b/services/core/java/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessor.java
@@ -56,12 +56,17 @@
// enables immediate failover to a secondary provider, one that might provide valid IDs for
// the same location, which should provide better behavior than just ignoring the event.
if (hasInvalidZones(event)) {
- TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder(
- event.getTimeZoneProviderStatus())
- .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
- .build();
- return TimeZoneProviderEvent.createUncertainEvent(
- event.getCreationElapsedMillis(), providerStatus);
+ TimeZoneProviderStatus providerStatus = event.getTimeZoneProviderStatus();
+ TimeZoneProviderStatus.Builder providerStatusBuilder;
+ if (providerStatus != null) {
+ providerStatusBuilder = new TimeZoneProviderStatus.Builder(providerStatus);
+ } else {
+ providerStatusBuilder = new TimeZoneProviderStatus.Builder();
+ }
+ return TimeZoneProviderEvent.createUncertainEvent(event.getCreationElapsedMillis(),
+ providerStatusBuilder
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
+ .build());
}
return event;
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 4da6585..de5e662 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -41,6 +41,7 @@
import android.os.SystemClock;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Slog;
@@ -265,9 +266,15 @@
return null;
}
+ if (Flags.throttleVibrationParamsRequests() && mVibrationParamRequest != null
+ && mVibrationParamRequest.usage == usage) {
+ // Reuse existing future for ongoing request with same usage.
+ return mVibrationParamRequest.future;
+ }
+
try {
endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
- mVibrationParamRequest = new VibrationParamRequest(uid);
+ mVibrationParamRequest = new VibrationParamRequest(uid, usage);
vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
mVibrationParamRequest.token);
return mVibrationParamRequest.future;
@@ -533,10 +540,12 @@
public final CompletableFuture<Void> future = new CompletableFuture<>();
public final IBinder token = new Binder();
public final int uid;
+ public final @VibrationAttributes.Usage int usage;
public final long uptimeMs;
- VibrationParamRequest(int uid) {
+ VibrationParamRequest(int uid, @VibrationAttributes.Usage int usage) {
this.uid = uid;
+ this.usage = usage;
uptimeMs = SystemClock.uptimeMillis();
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 72c7be3..ba2594a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3844,6 +3844,7 @@
pw.print(" mPadding="); pw.println(wpSize.mPadding);
});
pw.print(" mCropHint="); pw.println(wallpaper.cropHint);
+ if (multiCrop()) pw.print(" mCropHints="); pw.println(wallpaper.mCropHints);
pw.print(" mName="); pw.println(wallpaper.name);
pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup);
pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent);
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 09de01e..68f37380 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -43,7 +43,6 @@
import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.WindowInsets;
-import android.view.WindowInsetsController;
import android.view.WindowManager;
import android.window.ScreenCapture;
import android.window.SnapshotDrawerUtils;
@@ -137,7 +136,6 @@
}
abstract ActivityRecord getTopActivity(TYPE source);
- abstract WindowState getTopFullscreenWindow(TYPE source);
abstract ActivityManager.TaskDescription getTaskDescription(TYPE source);
/**
* Find the window for a given task to take a snapshot. Top child of the task is usually the one
@@ -331,7 +329,7 @@
builder.setPixelFormat(pixelFormat);
builder.setIsTranslucent(isTranslucent);
builder.setWindowingMode(source.getWindowingMode());
- builder.setAppearance(getAppearance(source));
+ builder.setAppearance(mainWindow.mAttrs.insetsFlags.appearance);
final Configuration taskConfig = activity.getTask().getConfiguration();
final int displayRotation = taskConfig.windowConfiguration.getDisplayRotation();
@@ -450,7 +448,7 @@
mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
contentInsets, letterboxInsets, false /* isLowResolution */,
false /* isRealSnapshot */, source.getWindowingMode(),
- getAppearance(source), false /* isTranslucent */, false /* hasImeSurface */);
+ attrs.insetsFlags.appearance, false /* isTranslucent */, false /* hasImeSurface */);
return validateSnapshot(taskSnapshot);
}
@@ -460,19 +458,6 @@
}
/**
- * @return The {@link WindowInsetsController.Appearance} flags for the top main app window in
- * the given {@param TYPE}.
- */
- @WindowInsetsController.Appearance
- private int getAppearance(TYPE source) {
- final WindowState topFullscreenWindow = getTopFullscreenWindow(source);
- if (topFullscreenWindow != null) {
- return topFullscreenWindow.mAttrs.insetsFlags.appearance;
- }
- return 0;
- }
-
- /**
* Called when an {@link ActivityRecord} has been removed.
*/
void onAppRemoved(ActivityRecord activity) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d039b04..7d70ea1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -495,7 +495,7 @@
final String launchedFromPackage; // always the package who started the activity.
@Nullable
final String launchedFromFeatureId; // always the feature in launchedFromPackage
- private final int mLaunchSourceType; // original launch source type
+ int mLaunchSourceType; // latest launch source type
final Intent intent; // the original intent that generated us
final String shortComponentName; // the short component name of the intent
final String resolvedType; // as per original caller;
@@ -2451,6 +2451,10 @@
return mLaunchSourceType == type;
}
+ void updateLaunchSourceType(int launchFromUid, WindowProcessController caller) {
+ mLaunchSourceType = determineLaunchSourceType(launchFromUid, caller);
+ }
+
private int determineLaunchSourceType(int launchFromUid, WindowProcessController caller) {
if (launchFromUid == Process.SYSTEM_UID || launchFromUid == Process.ROOT_UID) {
return LAUNCH_SOURCE_TYPE_SYSTEM;
@@ -4290,7 +4294,8 @@
}
void finishRelaunching() {
- mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(false);
+ mAppCompatController.getAppCompatOrientationOverrides()
+ .setRelaunchingAfterRequestedOrientationChanged(false);
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
if (mPendingRelaunchCount > 0) {
@@ -8181,7 +8186,8 @@
mLastReportedConfiguration.getMergedConfiguration())) {
ensureActivityConfiguration(false /* ignoreVisibility */);
if (mPendingRelaunchCount > originalRelaunchingCount) {
- mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(true);
+ mAppCompatController.getAppCompatOrientationOverrides()
+ .setRelaunchingAfterRequestedOrientationChanged(true);
}
if (mTransitionController.inPlayingTransition(this)) {
mTransitionController.mValidateActivityCompat.add(this);
@@ -8397,7 +8403,8 @@
*/
@ActivityInfo.SizeChangesSupportMode
private int supportsSizeChanges() {
- if (mLetterboxUiController.shouldOverrideForceNonResizeApp()) {
+ if (mAppCompatController.getAppCompatResizeOverrides()
+ .shouldOverrideForceNonResizeApp()) {
return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
}
@@ -8405,7 +8412,8 @@
return SIZE_CHANGES_SUPPORTED_METADATA;
}
- if (mLetterboxUiController.shouldOverrideForceResizeApp()) {
+ if (mAppCompatController.getAppCompatResizeOverrides()
+ .shouldOverrideForceResizeApp()) {
return SIZE_CHANGES_SUPPORTED_OVERRIDE;
}
@@ -10488,7 +10496,7 @@
mAppCompatController.getAppCompatOrientationOverrides()
.shouldIgnoreOrientationRequestLoop());
proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
- mLetterboxUiController.shouldOverrideForceResizeApp());
+ mAppCompatController.getAppCompatResizeOverrides().shouldOverrideForceResizeApp());
proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS,
mAppCompatController.getAppCompatAspectRatioOverrides()
.shouldEnableUserAspectRatioSettings());
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 48bc813..aa63393 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -590,10 +590,6 @@
return activity;
}
- WindowState getTopFullscreenWindow(ActivityRecord activity) {
- return activity.findMainWindow();
- }
-
@Override
ActivityManager.TaskDescription getTaskDescription(ActivityRecord object) {
return object.taskDescription;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5bfe9d7..c89f3a3 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1793,6 +1793,9 @@
activity.destroyIfPossible("Removes redundant singleInstance");
}
}
+ if (mLastStartActivityRecord != null) {
+ targetTaskTop.mLaunchSourceType = mLastStartActivityRecord.mLaunchSourceType;
+ }
targetTaskTop.mTransitionController.collect(targetTaskTop);
recordTransientLaunchIfNeeded(targetTaskTop);
// Recycle the target task for this launch.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a84598d..1c14c5d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5522,7 +5522,7 @@
final int procCount = procs.size();
for (int i = 0; i < procCount; i++) {
final int procUid = procs.keyAt(i);
- if (UserHandle.isApp(procUid) || !UserHandle.isSameUser(procUid, uid)) {
+ if (!UserHandle.isCore(procUid) || !UserHandle.isSameUser(procUid, uid)) {
// Don't use an app process or different user process for system component.
continue;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index b0d8925..afdbc0a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2801,6 +2801,13 @@
targetActivity, activityOptions);
}
+ if (callingPid > 0) {
+ final WindowProcessController wpc = mService.mProcessMap
+ .getProcess(callingPid);
+ if (wpc != null) {
+ targetActivity.updateLaunchSourceType(callingUid, wpc);
+ }
+ }
mService.getActivityStartController().postStartActivityProcessingForLastStarter(
task.getTopNonFinishingActivity(), ActivityManager.START_TASK_TO_FRONT,
task.getRootTask());
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 05d4c82..25cb134 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -36,6 +36,7 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
import android.annotation.NonNull;
import android.content.pm.PackageManager;
@@ -115,7 +116,7 @@
*/
boolean shouldOverrideMinAspectRatio() {
return mAllowMinAspectRatioOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO));
+ isChangeEnabled(mActivityRecord, OVERRIDE_MIN_ASPECT_RATIO));
}
/**
@@ -154,7 +155,7 @@
}
boolean isSystemOverrideToFullscreenEnabled() {
- return isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER)
+ return isChangeEnabled(mActivityRecord, OVERRIDE_ANY_ORIENTATION_TO_USER)
&& !mAllowOrientationOverrideOptProp.isFalse()
&& (mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_UNSET
|| mUserAspectRatioState.mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
@@ -302,10 +303,6 @@
private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET;
}
- private boolean isCompatChangeEnabled(long overrideChangeId) {
- return mActivityRecord.info.isChangeEnabled(overrideChangeId);
- }
-
private Resources getResources() {
return mActivityRecord.mWmService.mContext.getResources();
}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index 93a8663..aeaaffd 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -30,6 +30,7 @@
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
import android.annotation.NonNull;
import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode;
@@ -99,7 +100,8 @@
boolean shouldOverrideMinAspectRatioForCamera() {
return isCameraActive() && mAllowMinAspectRatioOverrideOptProp
.shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
+ isChangeEnabled(mActivityRecord,
+ OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA));
}
/**
@@ -115,7 +117,7 @@
*/
boolean shouldRefreshActivityForCameraCompat() {
return mCameraCompatAllowRefreshOptProp.shouldEnableWithOptOutOverrideAndProperty(
- isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH));
+ isChangeEnabled(mActivityRecord, OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH));
}
/**
@@ -134,7 +136,7 @@
*/
boolean shouldRefreshActivityViaPauseForCameraCompat() {
return mCameraCompatEnableRefreshViaPauseOptProp.shouldEnableWithOverrideAndProperty(
- isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE));
+ isChangeEnabled(mActivityRecord, OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE));
}
/**
@@ -150,7 +152,7 @@
*/
boolean shouldForceRotateForCameraCompat() {
return mCameraCompatAllowForceRotationOptProp.shouldEnableWithOptOutOverrideAndProperty(
- isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION));
+ isChangeEnabled(mActivityRecord, OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION));
}
/**
@@ -168,7 +170,7 @@
* </ul>
*/
boolean shouldApplyFreeformTreatmentForCameraCompat() {
- return Flags.cameraCompatForFreeform() && !isCompatChangeEnabled(
+ return Flags.cameraCompatForFreeform() && !isChangeEnabled(mActivityRecord,
OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
}
@@ -191,7 +193,7 @@
}
boolean isOverrideOrientationOnlyForCameraEnabled() {
- return isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
+ return isChangeEnabled(mActivityRecord, OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA);
}
/**
@@ -227,10 +229,6 @@
mAppCompatCameraOverridesState.mFreeformCameraCompatMode = freeformCameraCompatMode;
}
- private boolean isCompatChangeEnabled(long overrideChangeId) {
- return mActivityRecord.info.isChangeEnabled(overrideChangeId);
- }
-
static class AppCompatCameraOverridesState {
// Whether activity "refresh" was requested but not finished in
// ActivityRecord#activityResumedLocked following the camera compat force rotation in
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 998d65d..54223b6 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -87,6 +87,11 @@
return mAppCompatOverrides.getAppCompatAspectRatioOverrides();
}
+ @NonNull
+ AppCompatResizeOverrides getAppCompatResizeOverrides() {
+ return mAppCompatOverrides.getAppCompatResizeOverrides();
+ }
+
@Nullable
AppCompatCameraPolicy getAppCompatCameraPolicy() {
if (mActivityRecord.mDisplayContent != null) {
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index 0adf825..bd01351 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -20,14 +20,20 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
+import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.AppCompatUtils.asLazy;
+import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
import android.annotation.NonNull;
@@ -54,6 +60,10 @@
private final OptPropFactory.OptProp mIgnoreRequestedOrientationOptProp;
@NonNull
private final OptPropFactory.OptProp mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp;
+ @NonNull
+ private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp;
+ @NonNull
+ private final OptPropFactory.OptProp mAllowDisplayOrientationOverrideOptProp;
@NonNull
final OrientationOverridesState mOrientationOverridesState;
@@ -74,6 +84,17 @@
mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp = optPropBuilder.create(
PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED,
isPolicyForIgnoringRequestedOrientationEnabled);
+ mAllowOrientationOverrideOptProp = optPropBuilder.create(
+ PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
+ mAllowDisplayOrientationOverrideOptProp = optPropBuilder.create(
+ PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE,
+ () -> mActivityRecord.mDisplayContent != null
+ && mActivityRecord.getTask() != null
+ && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()
+ && !mActivityRecord.getTask().inMultiWindowMode()
+ && mActivityRecord.mDisplayContent.getNaturalOrientation()
+ == ORIENTATION_LANDSCAPE
+ );
}
boolean shouldEnableIgnoreOrientationRequest() {
@@ -81,6 +102,10 @@
isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION));
}
+ boolean isOverrideRespectRequestedOrientationEnabled() {
+ return isChangeEnabled(mActivityRecord, OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
+ }
+
/**
* Whether an app is calling {@link android.app.Activity#setRequestedOrientation}
* in a loop and orientation request should be ignored.
@@ -113,6 +138,26 @@
}
/**
+ * Whether should fix display orientation to landscape natural orientation when a task is
+ * fullscreen and the display is ignoring orientation requests.
+ *
+ * <p>This treatment is enabled when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Opt-in per-app override is enabled
+ * <li>Task is in fullscreen.
+ * <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled
+ * <li>Natural orientation of the display is landscape.
+ * </ul>
+ */
+ boolean shouldUseDisplayLandscapeNaturalOrientation() {
+ return mAllowDisplayOrientationOverrideOptProp
+ .shouldEnableWithOptInOverrideAndOptOutProperty(
+ isChangeEnabled(mActivityRecord,
+ OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION));
+ }
+
+ /**
* Sets whether an activity is relaunching after the app has called {@link
* android.app.Activity#setRequestedOrientation}.
*/
@@ -125,6 +170,10 @@
return mOrientationOverridesState.mIsRelaunchingAfterRequestedOrientationChanged;
}
+ boolean isAllowOrientationOverrideOptOut() {
+ return mAllowOrientationOverrideOptProp.isFalse();
+ }
+
@VisibleForTesting
int getSetOrientationRequestCounter() {
return mOrientationOverridesState.mSetOrientationRequestCounter;
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 17f0d97..c5506de 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -86,7 +86,8 @@
return SCREEN_ORIENTATION_PORTRAIT;
}
- if (mAppCompatOverrides.isAllowOrientationOverrideOptOut()) {
+ if (mAppCompatOverrides.getAppCompatOrientationOverrides()
+ .isAllowOrientationOverrideOptOut()) {
return candidate;
}
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index cde48d6..4450011 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -16,15 +16,6 @@
package com.android.server.wm;
-import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
-import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
-import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
-import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
-
import android.annotation.NonNull;
import com.android.server.wm.utils.OptPropFactory;
@@ -35,15 +26,6 @@
public class AppCompatOverrides {
@NonNull
- private final ActivityRecord mActivityRecord;
-
- @NonNull
- private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp;
- @NonNull
- private final OptPropFactory.OptProp mAllowDisplayOrientationOverrideOptProp;
- @NonNull
- private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp;
- @NonNull
private final AppCompatOrientationOverrides mAppCompatOrientationOverrides;
@NonNull
private final AppCompatCameraOverrides mAppCompatCameraOverrides;
@@ -51,39 +33,24 @@
private final AppCompatAspectRatioOverrides mAppCompatAspectRatioOverrides;
@NonNull
private final AppCompatFocusOverrides mAppCompatFocusOverrides;
+ @NonNull
+ private final AppCompatResizeOverrides mAppCompatResizeOverrides;
AppCompatOverrides(@NonNull ActivityRecord activityRecord,
@NonNull AppCompatConfiguration appCompatConfiguration,
@NonNull OptPropFactory optPropBuilder) {
- mActivityRecord = activityRecord;
-
- mAppCompatCameraOverrides = new AppCompatCameraOverrides(mActivityRecord,
+ mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
- mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(mActivityRecord,
+ mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
// TODO(b/341903757) Remove BooleanSuppliers after fixing dependency with reachability.
mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
appCompatConfiguration, optPropBuilder,
activityRecord.mLetterboxUiController::isDisplayFullScreenAndInPosture,
activityRecord.mLetterboxUiController::getHorizontalPositionMultiplier);
- mAppCompatFocusOverrides = new AppCompatFocusOverrides(mActivityRecord,
+ mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
-
- mAllowOrientationOverrideOptProp = optPropBuilder.create(
- PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
-
- mAllowDisplayOrientationOverrideOptProp = optPropBuilder.create(
- PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE,
- () -> mActivityRecord.mDisplayContent != null
- && mActivityRecord.getTask() != null
- && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()
- && !mActivityRecord.getTask().inMultiWindowMode()
- && mActivityRecord.mDisplayContent.getNaturalOrientation()
- == ORIENTATION_LANDSCAPE
- );
-
- mAllowForceResizeOverrideOptProp = optPropBuilder.create(
- PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ mAppCompatResizeOverrides = new AppCompatResizeOverrides(activityRecord, optPropBuilder);
}
@NonNull
@@ -106,66 +73,8 @@
return mAppCompatFocusOverrides;
}
- boolean isAllowOrientationOverrideOptOut() {
- return mAllowOrientationOverrideOptProp.isFalse();
- }
-
- boolean isOverrideRespectRequestedOrientationEnabled() {
- return isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
- }
-
- /**
- * Whether should fix display orientation to landscape natural orientation when a task is
- * fullscreen and the display is ignoring orientation requests.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Opt-in per-app override is enabled
- * <li>Task is in fullscreen.
- * <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled
- * <li>Natural orientation of the display is landscape.
- * </ul>
- */
- boolean shouldUseDisplayLandscapeNaturalOrientation() {
- return mAllowDisplayOrientationOverrideOptProp
- .shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION));
- }
-
- /**
- * Whether we should apply the force resize per-app override. When this override is applied it
- * forces the packages it is applied to to be resizable. It won't change whether the app can be
- * put into multi-windowing mode, but allow the app to resize without going into size-compat
- * mode when the window container resizes, such as display size change or screen rotation.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideForceResizeApp() {
- return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(FORCE_RESIZE_APP));
- }
-
- /**
- * Whether we should apply the force non resize per-app override. When this override is applied
- * it forces the packages it is applied to to be non-resizable.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideForceNonResizeApp() {
- return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
- isCompatChangeEnabled(FORCE_NON_RESIZE_APP));
- }
-
- private boolean isCompatChangeEnabled(long overrideChangeId) {
- return mActivityRecord.info.isChangeEnabled(overrideChangeId);
+ @NonNull
+ AppCompatResizeOverrides getAppCompatResizeOverrides() {
+ return mAppCompatResizeOverrides;
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java
new file mode 100644
index 0000000..60c1825
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java
@@ -0,0 +1,78 @@
+/*
+ * 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.wm;
+
+import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
+import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+
+import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
+
+import android.annotation.NonNull;
+
+import com.android.server.wm.utils.OptPropFactory;
+
+/**
+ * Encapsulate app compat logic about resizability.
+ */
+class AppCompatResizeOverrides {
+
+ @NonNull
+ private final ActivityRecord mActivityRecord;
+
+ @NonNull
+ private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp;
+
+ AppCompatResizeOverrides(@NonNull ActivityRecord activityRecord,
+ @NonNull OptPropFactory optPropBuilder) {
+ mActivityRecord = activityRecord;
+ mAllowForceResizeOverrideOptProp = optPropBuilder.create(
+ PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ }
+
+ /**
+ * Whether we should apply the force resize per-app override. When this override is applied it
+ * forces the packages it is applied to to be resizable. It won't change whether the app can be
+ * put into multi-windowing mode, but allow the app to resize without going into size-compat
+ * mode when the window container resizes, such as display size change or screen rotation.
+ *
+ * <p>This method returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Per-app override is enabled
+ * </ul>
+ */
+ boolean shouldOverrideForceResizeApp() {
+ return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
+ isChangeEnabled(mActivityRecord, FORCE_RESIZE_APP));
+ }
+
+ /**
+ * Whether we should apply the force non resize per-app override. When this override is applied
+ * it forces the packages it is applied to to be non-resizable.
+ *
+ * <p>This method returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Per-app override is enabled
+ * </ul>
+ */
+ boolean shouldOverrideForceNonResizeApp() {
+ return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
+ isChangeEnabled(mActivityRecord, FORCE_NON_RESIZE_APP));
+ }
+}
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index cbb210f..9996bbc 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -27,7 +27,6 @@
import static com.android.server.wm.LaunchParamsUtil.applyLayoutGravity;
import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 75724eb..86f69cd 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -267,7 +267,8 @@
// between fullscreen and PiP would work well. Checking TaskFragment rather than
// Task to ensure that Activity Embedding is excluded.
&& activity.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && activity.mLetterboxUiController.isOverrideRespectRequestedOrientationEnabled();
+ && activity.mAppCompatController.getAppCompatOrientationOverrides()
+ .isOverrideRespectRequestedOrientationEnabled();
}
boolean getIgnoreOrientationRequest() {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 475b473..403c307 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2939,8 +2939,9 @@
if (!handlesOrientationChangeFromDescendant(orientation)) {
ActivityRecord topActivity = topRunningActivity(/* considerKeyguardState= */ true);
- if (topActivity != null && topActivity.mLetterboxUiController
- .shouldUseDisplayLandscapeNaturalOrientation()) {
+ if (topActivity != null && topActivity.mAppCompatController
+ .getAppCompatOrientationOverrides()
+ .shouldUseDisplayLandscapeNaturalOrientation()) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Display id=%d is ignoring orientation request for %d, return %d"
+ " following a per-app override for %s",
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index ba74f50..59435b8 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -456,10 +456,6 @@
}
}
- InputChannel getInputChannel() {
- return mInputInterceptor == null ? null : mInputInterceptor.mClientChannel;
- }
-
InputWindowHandle getInputWindowHandle() {
return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle;
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 872b4e1..7a0fd3e 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -60,7 +60,6 @@
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -198,14 +197,6 @@
setWakeTransitionReady();
return;
}
- EventLogTags.writeWmSetKeyguardShown(
- displayId,
- keyguardShowing ? 1 : 0,
- aodShowing ? 1 : 0,
- state.mKeyguardGoingAway ? 1 : 0,
- state.mOccluded ? 1 : 0,
- "setKeyguardShown");
-
// Update the task snapshot if the screen will not be turned off. To make sure that the
// unlocking animation can animate consistent content. The conditions are:
// - Either AOD or keyguard changes to be showing. So if the states change individually,
@@ -224,6 +215,7 @@
state.mKeyguardShowing = keyguardShowing;
state.mAodShowing = aodShowing;
+ state.writeEventLog("setKeyguardShown");
if (keyguardChanged) {
// Irrelevant to AOD.
@@ -232,19 +224,13 @@
state.mDismissalRequested = false;
}
if (goingAwayRemoved
- || (Flags.keyguardAppearTransition() && keyguardShowing
- && !Display.isOffState(dc.getDisplayInfo().state))) {
+ || (keyguardShowing && !Display.isOffState(dc.getDisplayInfo().state))) {
// Keyguard decided to show or stopped going away. Send a transition to animate back
// to the locked state before holding the sleep token again
- final DisplayContent transitionDc = Flags.keyguardAppearTransition()
- ? dc
- : mRootWindowContainer.getDefaultDisplay();
- transitionDc.requestTransitionAndLegacyPrepare(
+ dc.requestTransitionAndLegacyPrepare(
TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
- if (Flags.keyguardAppearTransition()) {
- dc.mWallpaperController.adjustWallpaperWindows();
- }
- transitionDc.executeAppTransition();
+ dc.mWallpaperController.adjustWallpaperWindows();
+ dc.executeAppTransition();
}
}
@@ -284,13 +270,7 @@
mService.deferWindowLayout();
state.mKeyguardGoingAway = true;
try {
- EventLogTags.writeWmSetKeyguardShown(
- displayId,
- state.mKeyguardShowing ? 1 : 0,
- state.mAodShowing ? 1 : 0,
- 1 /* keyguardGoingAway */,
- state.mOccluded ? 1 : 0,
- "keyguardGoingAway");
+ state.writeEventLog("keyguardGoingAway");
final int transitFlags = convertTransitFlags(flags);
final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, transitFlags);
@@ -436,8 +416,9 @@
}
final TransitionController tc = mRootWindowContainer.mTransitionController;
+ final KeyguardDisplayState state = getDisplayState(displayId);
- final boolean occluded = getDisplayState(displayId).mOccluded;
+ final boolean occluded = state.mOccluded;
final boolean performTransition = isKeyguardLocked(displayId);
final boolean executeTransition = performTransition && !tc.isCollecting();
@@ -481,7 +462,7 @@
/**
* Called when keyguard going away state changed.
*/
- private void handleKeyguardGoingAwayChanged(DisplayContent dc) {
+ private void handleDismissInsecureKeyguard(DisplayContent dc) {
mService.deferWindowLayout();
try {
dc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, 0 /* transitFlags */);
@@ -646,6 +627,16 @@
mSleepTokenAcquirer.release(mDisplayId);
}
+ void writeEventLog(String reason) {
+ EventLogTags.writeWmSetKeyguardShown(
+ mDisplayId,
+ mKeyguardShowing ? 1 : 0,
+ mAodShowing ? 1 : 0,
+ mKeyguardGoingAway ? 1 : 0,
+ mOccluded ? 1 : 0,
+ reason);
+ }
+
/**
* Updates keyguard status if the top task could be visible. The top task may occlude
* keyguard, request to dismiss keyguard or make insecure keyguard go away based on its
@@ -715,23 +706,16 @@
}
boolean hasChange = false;
- if (lastOccluded != mOccluded) {
- if (mDisplayId == DEFAULT_DISPLAY) {
- EventLogTags.writeWmSetKeyguardShown(
- mDisplayId,
- mKeyguardShowing ? 1 : 0,
- mAodShowing ? 1 : 0,
- mKeyguardGoingAway ? 1 : 0,
- mOccluded ? 1 : 0,
- "updateVisibility");
- }
+ if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
+ writeEventLog("dismissIfInsecure");
+ controller.handleDismissInsecureKeyguard(display);
+ hasChange = true;
+ } else if (lastOccluded != mOccluded) {
controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
hasChange = true;
- } else if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
- controller.handleKeyguardGoingAwayChanged(display);
- hasChange = true;
}
- // Collect the participates for shell transition, so that transition won't happen too
+
+ // Collect the participants for shell transition, so that transition won't happen too
// early since the transition was set ready.
if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) {
display.mTransitionController.collect(top);
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 73f3655..444097a 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -116,67 +116,6 @@
}
}
- /**
- * Whether we should apply the force resize per-app override. When this override is applied it
- * forces the packages it is applied to to be resizable. It won't change whether the app can be
- * put into multi-windowing mode, but allow the app to resize without going into size-compat
- * mode when the window container resizes, such as display size change or screen rotation.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideForceResizeApp() {
- return getAppCompatOverrides().shouldOverrideForceResizeApp();
- }
-
- /**
- * Whether we should apply the force non resize per-app override. When this override is applied
- * it forces the packages it is applied to to be non-resizable.
- *
- * <p>This method returns {@code true} when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Per-app override is enabled
- * </ul>
- */
- boolean shouldOverrideForceNonResizeApp() {
- return getAppCompatOverrides().shouldOverrideForceNonResizeApp();
- }
-
- /**
- * Sets whether an activity is relaunching after the app has called {@link
- * android.app.Activity#setRequestedOrientation}.
- */
- void setRelaunchingAfterRequestedOrientationChanged(boolean isRelaunching) {
- getAppCompatOverrides().getAppCompatOrientationOverrides()
- .setRelaunchingAfterRequestedOrientationChanged(isRelaunching);
- }
-
-
- boolean isOverrideRespectRequestedOrientationEnabled() {
- return getAppCompatOverrides().isOverrideRespectRequestedOrientationEnabled();
- }
-
- /**
- * Whether should fix display orientation to landscape natural orientation when a task is
- * fullscreen and the display is ignoring orientation requests.
- *
- * <p>This treatment is enabled when the following conditions are met:
- * <ul>
- * <li>Opt-out component property isn't enabled
- * <li>Opt-in per-app override is enabled
- * <li>Task is in fullscreen.
- * <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled
- * <li>Natural orientation of the display is landscape.
- * </ul>
- */
- boolean shouldUseDisplayLandscapeNaturalOrientation() {
- return getAppCompatOverrides().shouldUseDisplayLandscapeNaturalOrientation();
- }
-
boolean hasWallpaperBackgroundForLetterbox() {
return mShowWallpaperForLetterboxBackground;
}
@@ -800,11 +739,6 @@
return null;
}
- boolean getIsRelaunchingAfterRequestedOrientationChanged() {
- return getAppCompatOverrides().getAppCompatOrientationOverrides()
- .getIsRelaunchingAfterRequestedOrientationChanged();
- }
-
private void adjustBoundsForTaskbar(final WindowState mainWindow, final Rect bounds) {
// Rounded corners should be displayed above the taskbar. When taskbar is hidden,
// an insets frame is equal to a navigation bar which shouldn't affect position of
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index a1e6701..2da1fec 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -148,8 +148,7 @@
}
// For snapshot surface, the top activity could be trampoline activity, so here should
// search for top fullscreen activity in the task.
- final WindowState mainWindow = task
- .getTopFullscreenMainWindow(false /* includeStartingApp */);
+ final WindowState mainWindow = task.getTopFullscreenMainWindow();
if (mainWindow == null) {
Slog.w(TAG, "TaskSnapshotSurface.create: no main window in " + activity);
return null;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 74adeb9..1c9aaf9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1132,6 +1132,9 @@
final Task oldParentTask = oldParent.asTask();
if (oldParentTask != null) {
forAllActivities(oldParentTask::cleanUpActivityReferences);
+
+ // Update the task description of the previous parent as well
+ oldParentTask.updateTaskDescription();
}
if (newParent == null || !newParent.inPinnedWindowingMode()) {
@@ -1163,6 +1166,9 @@
} catch (RemoteException e) {
}
}
+
+ // Update the ancestor tasks' task description after reparenting
+ updateTaskDescription();
}
// First time we are adding the task to the system.
@@ -2281,7 +2287,7 @@
// Apply crop to root tasks only and clear the crops of the descendant tasks.
int width = 0;
int height = 0;
- if (isRootTask()) {
+ if (isRootTask() && !mTransitionController.mIsWaitingForDisplayEnabled) {
final Rect taskBounds = getBounds();
width = taskBounds.width();
height = taskBounds.height();
@@ -3007,17 +3013,9 @@
return r.getTask().mTaskId != taskId && r.token != notTop && r.canBeTopRunning();
}
- WindowState getTopFullscreenMainWindow(boolean includeStartingApp) {
- final WindowState[] candidate = new WindowState[1];
- getActivity((r) -> {
- final WindowState win = r.findMainWindow(includeStartingApp);
- if (win != null && win.mAttrs.isFullscreen()) {
- candidate[0] = win;
- return true;
- }
- return false;
- });
- return candidate[0];
+ @Nullable
+ WindowState getTopFullscreenMainWindow() {
+ return getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION && w.mAttrs.isFullscreen());
}
/**
@@ -3361,7 +3359,7 @@
//TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child
// order changes.
- final Task topTask = top != null ? top.getTask() : this;
+ final Task topTask = top != null && top.getTask() != null ? top.getTask() : this;
info.resizeMode = topTask.mResizeMode;
info.topActivityType = topTask.getActivityType();
info.displayCutoutInsets = topTask.getDisplayCutoutInsets();
@@ -3415,6 +3413,7 @@
info.isSleeping = shouldSleepActivities();
info.isTopActivityTransparent = top != null && !top.fillsParent();
info.isTopActivityStyleFloating = top != null && top.isStyleFloating();
+ info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
appCompatTaskInfo.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
appCompatTaskInfo.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET;
appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET;
@@ -3593,8 +3592,7 @@
// starting window because persisted configuration does not effect to Task.
info.taskInfo.configuration.setTo(activity.getConfiguration());
if (!Flags.drawSnapshotAspectRatioMatch()) {
- final WindowState mainWindow =
- getTopFullscreenMainWindow(false /* includeStartingApp */);
+ final WindowState mainWindow = getTopFullscreenMainWindow();
if (mainWindow != null) {
info.topOpaqueWindowInsetsState =
mainWindow.getInsetsStateWithVisibilityOverride();
@@ -6147,9 +6145,8 @@
@Override
void onChildPositionChanged(WindowContainer child) {
- dispatchTaskInfoChangedIfNeeded(false /* force */);
-
if (!mChildren.contains(child)) {
+ dispatchTaskInfoChangedIfNeeded(false /* force */);
return;
}
if (child.asTask() != null) {
@@ -6161,6 +6158,10 @@
// Send for TaskFragmentParentInfo#hasDirectActivity change.
sendTaskFragmentParentInfoChangedIfNeeded();
}
+
+ // Update the ancestor tasks' task description after any children have reparented
+ updateTaskDescription();
+ dispatchTaskInfoChangedIfNeeded(false /* force */);
}
void reparent(TaskDisplayArea newParent, boolean onTop) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 235deb2..1f82cdb 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -267,11 +267,6 @@
}
@Override
- WindowState getTopFullscreenWindow(Task source) {
- return source.getTopFullscreenMainWindow(true /* includeStartingApp */);
- }
-
- @Override
ActivityManager.TaskDescription getTaskDescription(Task source) {
return source.getTaskDescription();
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index 34b9913..2c58c61 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -97,10 +97,10 @@
/**
* @return If a window animation has outsets applied to it.
- * @see Animation#hasExtension()
+ * @see Animation#getExtensionEdges()
*/
public boolean hasExtension() {
- return mAnimation.hasExtension();
+ return mAnimation.getExtensionEdges() != 0;
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 8ae1cf0..c6aaf4e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -380,7 +380,7 @@
InputChannel source) {
return state.register(display)
.thenApply(unused ->
- service.startDragAndDrop(source, state.getInputChannel()));
+ service.startDragAndDrop(source.getToken(), state.getInputToken()));
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 164994c..1cc5a8b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5373,7 +5373,7 @@
// change then delay the position update until it has redrawn to avoid any flickers.
final boolean isLetterboxedAndRelaunching = activityRecord != null
&& activityRecord.areBoundsLetterboxed()
- && activityRecord.mLetterboxUiController
+ && activityRecord.mAppCompatController.getAppCompatOrientationOverrides()
.getIsRelaunchingAfterRequestedOrientationChanged();
if (surfaceResizedWithoutMoveAnimation || isLetterboxedAndRelaunching) {
applyWithNextDraw(mSetSurfacePositionConsumer);
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 337d5c1..8981b7a 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -49,6 +49,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.wm.ImeTargetChangeListener;
import com.android.server.wm.WindowManagerInternal;
@@ -93,33 +94,37 @@
@Test
public void testRequestImeVisibility_showImplicit() {
- initImeTargetWindowState(mWindowToken);
- boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
- InputMethodManager.SHOW_IMPLICIT);
- mComputer.requestImeVisibility(mWindowToken, res);
+ synchronized (ImfLock.class) {
+ initImeTargetWindowState(mWindowToken);
+ boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
+ InputMethodManager.SHOW_IMPLICIT);
+ mComputer.requestImeVisibility(mWindowToken, res);
- final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
- assertThat(state).isNotNull();
- assertThat(state.hasEditorFocused()).isTrue();
- assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
- assertThat(state.isRequestedImeVisible()).isTrue();
+ final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+ assertThat(state).isNotNull();
+ assertThat(state.hasEditorFocused()).isTrue();
+ assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+ assertThat(state.isRequestedImeVisible()).isTrue();
- assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ }
}
@Test
public void testRequestImeVisibility_showExplicit() {
- initImeTargetWindowState(mWindowToken);
- boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
- mComputer.requestImeVisibility(mWindowToken, res);
+ synchronized (ImfLock.class) {
+ initImeTargetWindowState(mWindowToken);
+ boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
+ mComputer.requestImeVisibility(mWindowToken, res);
- final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
- assertThat(state).isNotNull();
- assertThat(state.hasEditorFocused()).isTrue();
- assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
- assertThat(state.isRequestedImeVisible()).isTrue();
+ final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+ assertThat(state).isNotNull();
+ assertThat(state.hasEditorFocused()).isTrue();
+ assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+ assertThat(state.isRequestedImeVisible()).isTrue();
- assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+ assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+ }
}
/**
@@ -128,12 +133,14 @@
*/
@Test
public void testRequestImeVisibility_showExplicit_thenShowImplicit() {
- initImeTargetWindowState(mWindowToken);
- mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
- assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+ synchronized (ImfLock.class) {
+ initImeTargetWindowState(mWindowToken);
+ mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
+ assertThat(mComputer.mRequestedShowExplicitly).isTrue();
- mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
- assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+ mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT);
+ assertThat(mComputer.mRequestedShowExplicitly).isTrue();
+ }
}
/**
@@ -142,162 +149,183 @@
*/
@Test
public void testRequestImeVisibility_showForced_thenShowExplicit() {
- initImeTargetWindowState(mWindowToken);
- mComputer.onImeShowFlags(ImeTracker.Token.empty(), InputMethodManager.SHOW_FORCED);
- assertThat(mComputer.mShowForced).isTrue();
+ synchronized (ImfLock.class) {
+ initImeTargetWindowState(mWindowToken);
+ mComputer.onImeShowFlags(ImeTracker.Token.empty(), InputMethodManager.SHOW_FORCED);
+ assertThat(mComputer.mShowForced).isTrue();
- mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
- assertThat(mComputer.mShowForced).isTrue();
+ mComputer.onImeShowFlags(ImeTracker.Token.empty(), 0 /* showFlags */);
+ assertThat(mComputer.mShowForced).isTrue();
+ }
}
@Test
public void testRequestImeVisibility_showImplicit_a11yNoImePolicy() {
- // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy
- mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN);
+ synchronized (ImfLock.class) {
+ // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy
+ mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN);
- initImeTargetWindowState(mWindowToken);
- boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
- InputMethodManager.SHOW_IMPLICIT);
- mComputer.requestImeVisibility(mWindowToken, res);
+ initImeTargetWindowState(mWindowToken);
+ boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
+ InputMethodManager.SHOW_IMPLICIT);
+ mComputer.requestImeVisibility(mWindowToken, res);
- final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
- assertThat(state).isNotNull();
- assertThat(state.hasEditorFocused()).isTrue();
- assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
- assertThat(state.isRequestedImeVisible()).isFalse();
+ final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+ assertThat(state).isNotNull();
+ assertThat(state.hasEditorFocused()).isTrue();
+ assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+ assertThat(state.isRequestedImeVisible()).isFalse();
- assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ }
}
@Test
public void testRequestImeVisibility_showImplicit_imeHiddenPolicy() {
- // Precondition: set IME hidden display policy before calling showSoftInput
- mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
+ synchronized (ImfLock.class) {
+ // Precondition: set IME hidden display policy before calling showSoftInput
+ mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
- initImeTargetWindowState(mWindowToken);
- boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
- InputMethodManager.SHOW_IMPLICIT);
- mComputer.requestImeVisibility(mWindowToken, res);
+ initImeTargetWindowState(mWindowToken);
+ boolean res = mComputer.onImeShowFlags(ImeTracker.Token.empty(),
+ InputMethodManager.SHOW_IMPLICIT);
+ mComputer.requestImeVisibility(mWindowToken, res);
- final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
- assertThat(state).isNotNull();
- assertThat(state.hasEditorFocused()).isTrue();
- assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
- assertThat(state.isRequestedImeVisible()).isFalse();
+ final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+ assertThat(state).isNotNull();
+ assertThat(state.hasEditorFocused()).isTrue();
+ assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+ assertThat(state.isRequestedImeVisible()).isFalse();
- assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ assertThat(mComputer.mRequestedShowExplicitly).isFalse();
+ }
}
@Test
public void testRequestImeVisibility_hideNotAlways() {
- // Precondition: ensure IME has shown before hiding request.
- mComputer.setInputShown(true);
+ synchronized (ImfLock.class) {
+ // Precondition: ensure IME has shown before hiding request.
+ mComputer.setInputShown(true);
- initImeTargetWindowState(mWindowToken);
- assertThat(mComputer.canHideIme(ImeTracker.Token.empty(),
- InputMethodManager.HIDE_NOT_ALWAYS)).isTrue();
- mComputer.requestImeVisibility(mWindowToken, false);
+ initImeTargetWindowState(mWindowToken);
+ assertThat(mComputer.canHideIme(ImeTracker.Token.empty(),
+ InputMethodManager.HIDE_NOT_ALWAYS)).isTrue();
+ mComputer.requestImeVisibility(mWindowToken, false);
- final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
- assertThat(state).isNotNull();
- assertThat(state.hasEditorFocused()).isTrue();
- assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
- assertThat(state.isRequestedImeVisible()).isFalse();
+ final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
+ assertThat(state).isNotNull();
+ assertThat(state.hasEditorFocused()).isTrue();
+ assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
+ assertThat(state.isRequestedImeVisible()).isFalse();
+ }
}
@Test
public void testComputeImeDisplayId() {
- final ImeTargetWindowState state = mComputer.getOrCreateWindowState(mWindowToken);
+ synchronized (ImfLock.class) {
+ final ImeTargetWindowState state = mComputer.getOrCreateWindowState(mWindowToken);
- mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL;
- mComputer.computeImeDisplayId(state, DEFAULT_DISPLAY);
- assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
- assertThat(state.getImeDisplayId()).isEqualTo(DEFAULT_DISPLAY);
+ mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL;
+ mComputer.computeImeDisplayId(state, DEFAULT_DISPLAY);
+ assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
+ assertThat(state.getImeDisplayId()).isEqualTo(DEFAULT_DISPLAY);
- mComputer.computeImeDisplayId(state, 10 /* displayId */);
- assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
- assertThat(state.getImeDisplayId()).isEqualTo(10);
+ mComputer.computeImeDisplayId(state, 10 /* displayId */);
+ assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
+ assertThat(state.getImeDisplayId()).isEqualTo(10);
- mImeDisplayPolicy = DISPLAY_IME_POLICY_HIDE;
- mComputer.computeImeDisplayId(state, 10 /* displayId */);
- assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isTrue();
- assertThat(state.getImeDisplayId()).isEqualTo(INVALID_DISPLAY);
+ mImeDisplayPolicy = DISPLAY_IME_POLICY_HIDE;
+ mComputer.computeImeDisplayId(state, 10 /* displayId */);
+ assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isTrue();
+ assertThat(state.getImeDisplayId()).isEqualTo(INVALID_DISPLAY);
- mImeDisplayPolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
- mComputer.computeImeDisplayId(state, 10 /* displayId */);
- assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
- assertThat(state.getImeDisplayId()).isEqualTo(FALLBACK_DISPLAY_ID);
+ mImeDisplayPolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
+ mComputer.computeImeDisplayId(state, 10 /* displayId */);
+ assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse();
+ assertThat(state.getImeDisplayId()).isEqualTo(FALLBACK_DISPLAY_ID);
+ }
}
@Test
public void testComputeState_lastImeRequestedVisible_preserved_When_StateUnChanged() {
- // Assume the last IME targeted window has requested IME visible
- final IBinder lastImeTargetWindowToken = new Binder();
- mInputMethodManagerService.mLastImeTargetWindow = lastImeTargetWindowToken;
- mComputer.requestImeVisibility(lastImeTargetWindowToken, true);
- final ImeTargetWindowState lastState = mComputer.getWindowStateOrNull(
- lastImeTargetWindowToken);
- assertThat(lastState.isRequestedImeVisible()).isTrue();
+ synchronized (ImfLock.class) {
+ // Assume the last IME targeted window has requested IME visible
+ final IBinder lastImeTargetWindowToken = new Binder();
+ mComputer.setLastImeTargetWindow(lastImeTargetWindowToken);
+ mComputer.requestImeVisibility(lastImeTargetWindowToken, true);
+ final ImeTargetWindowState lastState = mComputer.getWindowStateOrNull(
+ lastImeTargetWindowToken);
+ assertThat(lastState.isRequestedImeVisible()).isTrue();
- // Verify when focusing the next window with STATE_UNCHANGED flag, the last IME
- // visibility state will be preserved to the current window state.
- final ImeTargetWindowState stateWithUnChangedFlag = initImeTargetWindowState(mWindowToken);
- mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */);
- assertThat(stateWithUnChangedFlag.isRequestedImeVisible()).isEqualTo(
- lastState.isRequestedImeVisible());
+ // Verify when focusing the next window with STATE_UNCHANGED flag, the last IME
+ // visibility state will be preserved to the current window state.
+ final ImeTargetWindowState stateWithUnChangedFlag = initImeTargetWindowState(
+ mWindowToken);
+ mComputer.computeState(stateWithUnChangedFlag, true /* allowVisible */);
+ assertThat(stateWithUnChangedFlag.isRequestedImeVisible()).isEqualTo(
+ lastState.isRequestedImeVisible());
+ }
}
@Test
public void testOnInteractiveChanged() {
- mComputer.getOrCreateWindowState(mWindowToken);
- // Precondition: ensure IME has shown before hiding request.
- mComputer.requestImeVisibility(mWindowToken, true);
- mComputer.setInputShown(true);
+ synchronized (ImfLock.class) {
+ mComputer.getOrCreateWindowState(mWindowToken);
+ // Precondition: ensure IME has shown before hiding request.
+ mComputer.requestImeVisibility(mWindowToken, true);
+ mComputer.setInputShown(true);
- // No need any visibility change When initially shows IME on the device was interactive.
- ImeVisibilityStateComputer.ImeVisibilityResult result = mComputer.onInteractiveChanged(
- mWindowToken, true /* interactive */);
- assertThat(result).isNull();
+ // No need any visibility change When initially shows IME on the device was interactive.
+ ImeVisibilityStateComputer.ImeVisibilityResult result = mComputer.onInteractiveChanged(
+ mWindowToken, true /* interactive */);
+ assertThat(result).isNull();
- // Show the IME screenshot to capture the last IME visible state when the device inactive.
- result = mComputer.onInteractiveChanged(mWindowToken, false /* interactive */);
- assertThat(result).isNotNull();
- assertThat(result.getState()).isEqualTo(STATE_SHOW_IME_SNAPSHOT);
- assertThat(result.getReason()).isEqualTo(SHOW_IME_SCREENSHOT_FROM_IMMS);
+ // Show the IME screenshot to capture the last IME visible state when the device
+ // inactive.
+ result = mComputer.onInteractiveChanged(mWindowToken, false /* interactive */);
+ assertThat(result).isNotNull();
+ assertThat(result.getState()).isEqualTo(STATE_SHOW_IME_SNAPSHOT);
+ assertThat(result.getReason()).isEqualTo(SHOW_IME_SCREENSHOT_FROM_IMMS);
- // Remove the IME screenshot when the device became interactive again.
- result = mComputer.onInteractiveChanged(mWindowToken, true /* interactive */);
- assertThat(result).isNotNull();
- assertThat(result.getState()).isEqualTo(STATE_REMOVE_IME_SNAPSHOT);
- assertThat(result.getReason()).isEqualTo(REMOVE_IME_SCREENSHOT_FROM_IMMS);
+ // Remove the IME screenshot when the device became interactive again.
+ result = mComputer.onInteractiveChanged(mWindowToken, true /* interactive */);
+ assertThat(result).isNotNull();
+ assertThat(result.getState()).isEqualTo(STATE_REMOVE_IME_SNAPSHOT);
+ assertThat(result.getReason()).isEqualTo(REMOVE_IME_SCREENSHOT_FROM_IMMS);
+ }
}
@Test
public void testOnApplyImeVisibilityFromComputer() {
- final IBinder testImeTargetOverlay = new Binder();
- final IBinder testImeInputTarget = new Binder();
+ synchronized (ImfLock.class) {
+ final IBinder testImeTargetOverlay = new Binder();
+ final IBinder testImeInputTarget = new Binder();
- // Simulate a test IME input target was visible.
- mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, true, false);
+ // Simulate a test IME input target was visible.
+ mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, true, false);
- // Simulate a test IME layering target overlay fully occluded the IME input target.
- mListener.onImeTargetOverlayVisibilityChanged(testImeTargetOverlay,
- TYPE_APPLICATION_OVERLAY, true, false);
- mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, false, false);
- final ArgumentCaptor<IBinder> targetCaptor = ArgumentCaptor.forClass(IBinder.class);
- final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass(
- ImeVisibilityResult.class);
- verify(mInputMethodManagerService).onApplyImeVisibilityFromComputer(targetCaptor.capture(),
- notNull() /* statsToken */, resultCaptor.capture());
- final IBinder imeInputTarget = targetCaptor.getValue();
- final ImeVisibilityResult result = resultCaptor.getValue();
+ // Simulate a test IME layering target overlay fully occluded the IME input target.
+ mListener.onImeTargetOverlayVisibilityChanged(testImeTargetOverlay,
+ TYPE_APPLICATION_OVERLAY, true, false);
+ mListener.onImeInputTargetVisibilityChanged(testImeInputTarget, false, false);
+ final ArgumentCaptor<IBinder> targetCaptor = ArgumentCaptor.forClass(IBinder.class);
+ final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass(
+ ImeVisibilityResult.class);
+ synchronized (ImfLock.class) {
+ verify(mInputMethodManagerService).onApplyImeVisibilityFromComputerLocked(
+ targetCaptor.capture(), notNull() /* statsToken */, resultCaptor.capture());
+ }
+ final IBinder imeInputTarget = targetCaptor.getValue();
+ final ImeVisibilityResult result = resultCaptor.getValue();
- // Verify the computer will callback hiding IME state to IMMS.
- assertThat(imeInputTarget).isEqualTo(testImeInputTarget);
- assertThat(result.getState()).isEqualTo(STATE_HIDE_IME_EXPLICIT);
- assertThat(result.getReason()).isEqualTo(HIDE_WHEN_INPUT_TARGET_INVISIBLE);
+ // Verify the computer will callback hiding IME state to IMMS.
+ assertThat(imeInputTarget).isEqualTo(testImeInputTarget);
+ assertThat(result.getState()).isEqualTo(STATE_HIDE_IME_EXPLICIT);
+ assertThat(result.getReason()).isEqualTo(HIDE_WHEN_INPUT_TARGET_INVISIBLE);
+ }
}
+ @GuardedBy("ImfLock.class")
private ImeTargetWindowState initImeTargetWindowState(IBinder windowToken) {
final ImeTargetWindowState state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNCHANGED,
0, true, true, true);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java
new file mode 100644
index 0000000..944b7c6
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImmutableSparseArrayTest.java
@@ -0,0 +1,278 @@
+/*
+ * 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public final class ImmutableSparseArrayTest {
+
+ @Test
+ public void testEmptyObject() {
+ final ImmutableSparseArray<Object> empty = ImmutableSparseArray.empty();
+
+ assertThat(empty.size()).isEqualTo(0);
+ verifyCommonBehaviors(empty);
+ }
+
+ @Test
+ public void testEmptyMethod() {
+ assertThat(ImmutableSparseArray.empty()).isSameInstanceAs(ImmutableSparseArray.empty());
+ }
+
+ @Test
+ public void testCloneWithPutOrSelf_appendingFromEmpty() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = -2; // intentionally negative
+ final Object value2 = new Object();
+ final int key3 = -3; // intentionally negative
+ final Object value3 = new Object();
+ final int key4 = 4;
+ final Object value4 = new Object();
+
+ final ImmutableSparseArray<Object> oneItemArray = ImmutableSparseArray.empty()
+ .cloneWithPutOrSelf(key1, value1);
+ verifyCommonBehaviors(oneItemArray);
+ assertThat(oneItemArray.size()).isEqualTo(1);
+ assertThat(oneItemArray.get(key1)).isSameInstanceAs(value1);
+
+ final ImmutableSparseArray<Object> twoItemArray =
+ oneItemArray.cloneWithPutOrSelf(key2, value2);
+ assertThat(twoItemArray).isNotSameInstanceAs(oneItemArray);
+ verifyCommonBehaviors(twoItemArray);
+ assertThat(twoItemArray.size()).isEqualTo(2);
+ assertThat(twoItemArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(twoItemArray.get(key2)).isSameInstanceAs(value2);
+
+ final ImmutableSparseArray<Object> threeItemArray =
+ twoItemArray.cloneWithPutOrSelf(key3, value3);
+ assertThat(threeItemArray).isNotSameInstanceAs(twoItemArray);
+ verifyCommonBehaviors(threeItemArray);
+ assertThat(threeItemArray.size()).isEqualTo(3);
+ assertThat(threeItemArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(threeItemArray.get(key2)).isSameInstanceAs(value2);
+ assertThat(threeItemArray.get(key3)).isSameInstanceAs(value3);
+
+ final ImmutableSparseArray<Object> fourItemArray =
+ threeItemArray.cloneWithPutOrSelf(key4, value4);
+ assertThat(fourItemArray).isNotSameInstanceAs(threeItemArray);
+ verifyCommonBehaviors(fourItemArray);
+ assertThat(fourItemArray.size()).isEqualTo(4);
+ assertThat(fourItemArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(fourItemArray.get(key2)).isSameInstanceAs(value2);
+ assertThat(fourItemArray.get(key3)).isSameInstanceAs(value3);
+ assertThat(fourItemArray.get(key4)).isSameInstanceAs(value4);
+ }
+
+ @Test
+ public void testCloneWithPutOrSelf_returnSelf() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1);
+ assertThat(array.cloneWithPutOrSelf(key1, value1)).isSameInstanceAs(array);
+ }
+
+ @Test
+ public void testCloneWithPutOrSelf_updateExistingValue() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final Object value2updated = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3);
+
+ final var updatedArray = array.cloneWithPutOrSelf(key2, value2updated);
+ verifyCommonBehaviors(updatedArray);
+
+ assertThat(updatedArray.size()).isEqualTo(3);
+ assertThat(updatedArray.get(key1)).isSameInstanceAs(value1);
+ assertThat(updatedArray.get(key2)).isSameInstanceAs(value2updated);
+ assertThat(updatedArray.get(key3)).isSameInstanceAs(value3);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_empty() {
+ final ImmutableSparseArray<Object> empty = ImmutableSparseArray.empty();
+ assertThat(empty.cloneWithRemoveOrSelf(0)).isSameInstanceAs(empty);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_singleInstance() {
+ final int key = 1;
+ final Object value = new Object();
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key, value);
+ assertThat(array.cloneWithRemoveOrSelf(key)).isSameInstanceAs(ImmutableSparseArray.empty());
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_firstItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3)
+ .cloneWithRemoveOrSelf(key1);
+ verifyCommonBehaviors(array);
+
+ assertThat(array.size()).isEqualTo(2);
+ assertThat(array.get(key1)).isNull();
+ assertThat(array.get(key2)).isSameInstanceAs(value2);
+ assertThat(array.get(key3)).isSameInstanceAs(value3);
+ assertThat(array.keyAt(0)).isEqualTo(key2);
+ assertThat(array.keyAt(1)).isEqualTo(key3);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_lastItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3)
+ .cloneWithRemoveOrSelf(key3);
+ verifyCommonBehaviors(array);
+
+ assertThat(array.size()).isEqualTo(2);
+ assertThat(array.get(key1)).isSameInstanceAs(value1);
+ assertThat(array.get(key2)).isSameInstanceAs(value2);
+ assertThat(array.get(key3)).isNull();
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_middleItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3)
+ .cloneWithRemoveOrSelf(key2);
+ verifyCommonBehaviors(array);
+
+ assertThat(array.size()).isEqualTo(2);
+ assertThat(array.get(key1)).isSameInstanceAs(value1);
+ assertThat(array.get(key2)).isNull();
+ assertThat(array.get(key3)).isSameInstanceAs(value3);
+ }
+
+ @Test
+ public void testCloneWithRemoveOrSelf_nonExistentItem() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+ final int key4 = 4;
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3);
+
+ assertThat(array.cloneWithRemoveOrSelf(key4)).isSameInstanceAs(array);
+ }
+
+ @Test
+ public void testForEach() {
+ final int key1 = 1;
+ final Object value1 = new Object();
+ final int key2 = 2;
+ final Object value2 = new Object();
+ final int key3 = 3;
+ final Object value3 = new Object();
+
+ final ImmutableSparseArray<Object> array = ImmutableSparseArray
+ .empty()
+ .cloneWithPutOrSelf(key1, value1)
+ .cloneWithPutOrSelf(key2, value2)
+ .cloneWithPutOrSelf(key3, value3);
+
+ final ArrayList<Object> list = new ArrayList<>();
+ array.forEach(list::add);
+ assertThat(list).containsExactlyElementsIn(new Object[]{ value1, value2, value3 })
+ .inOrder();
+ }
+
+
+ private void verifyCommonBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+ verifyInvalidKeyBehaviors(sparseArray);
+ verifyOutOfBoundsBehaviors(sparseArray);
+ }
+
+ private void verifyInvalidKeyBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+ final int invalid_key = -123456678;
+ assertThat(sparseArray.get(invalid_key)).isNull();
+ assertThat(sparseArray.indexOfKey(invalid_key)).isEqualTo(-1);
+ }
+
+ private void verifyOutOfBoundsBehaviors(@NonNull ImmutableSparseArray<Object> sparseArray) {
+ final int size = sparseArray.size();
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.keyAt(size));
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.valueAt(size));
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.keyAt(-1));
+ assertThrows(ArrayIndexOutOfBoundsException.class,
+ () -> sparseArray.valueAt(-1));
+ }
+}
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 ec9bfa7..d427a6d 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -272,7 +272,7 @@
// Certain tests rely on TEST_IME_ID that is installed with AndroidTest.xml.
// TODO(b/352615651): Consider just synthesizing test InputMethodInfo then injecting it.
- AdditionalSubtypeMapRepository.ensureInitializedAndGet(mCallingUserId);
+ AdditionalSubtypeMapRepository.initializeIfNecessary(mCallingUserId);
final var settings = InputMethodManagerService.queryInputMethodServicesInternal(mContext,
mCallingUserId, AdditionalSubtypeMapRepository.get(mCallingUserId),
DirectBootAwareness.AUTO);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
index 98ba9ae..abb354b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayStatsServiceTest.java
@@ -151,9 +151,10 @@
}
@Test
- public void testDisplayInteractivityChanges(
+ public void testDisplayInteractivityChangesWhileMirroring(
@TestParameter final boolean isExternalDisplayUsedForAudio) {
mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
+ mExternalDisplayStatsService.onDisplayAdded(EXTERNAL_DISPLAY_ID);
mHandler.flush();
assertThat(mInteractivityReceiver).isNotNull();
@@ -180,12 +181,38 @@
mInteractivityReceiver.onReceive(null, null);
assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
verify(mMockedInjector).writeLog(FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED,
- FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__CONNECTED,
+ FrameworkStatsLog.EXTERNAL_DISPLAY_STATE_CHANGED__STATE__MIRRORING,
/*numberOfDisplays=*/ 1,
isExternalDisplayUsedForAudio);
}
@Test
+ public void testDisplayInteractivityChangesWhileConnected() {
+ mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
+ mHandler.flush();
+ assertThat(mInteractivityReceiver).isNotNull();
+ clearInvocations(mMockedInjector);
+
+ // Default is 'interactive', so no log should be written.
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+
+ // Change to non-interactive should not produce log
+ when(mMockedInjector.isInteractive(eq(EXTERNAL_DISPLAY_ID))).thenReturn(false);
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isFalse();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+ clearInvocations(mMockedInjector);
+
+ // Change back to interactive should not produce log
+ when(mMockedInjector.isInteractive(eq(EXTERNAL_DISPLAY_ID))).thenReturn(true);
+ mInteractivityReceiver.onReceive(null, null);
+ assertThat(mExternalDisplayStatsService.isInteractiveExternalDisplays()).isTrue();
+ verify(mMockedInjector, never()).writeLog(anyInt(), anyInt(), anyInt(), anyBoolean());
+ }
+
+ @Test
public void testAudioPlaybackChanges() {
mExternalDisplayStatsService.onDisplayConnected(mMockedLogicalDisplay);
mHandler.flush();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index adcbf5c..194bf4b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -442,20 +442,24 @@
IMPORTANCE_FOREGROUND_SERVICE, // importance
null); // description
- // Case 4: Create a process from another package with kill from lmkd
+ /*
+ * Case 4: Create a process from another package with kill from lmkd
+ * We expect LMKD's reported RSS to be the process' last seen RSS.
+ */
final int app2UidUser2 = 1010234;
final int app2PidUser2 = 12348;
final long app2Pss1 = 54321;
final long app2Rss1 = 65432;
+ final long lmkd_reported_rss = 43215;
final String app2ProcessName = "com.android.test.stub2:process";
final String app2PackageName = "com.android.test.stub2";
sleep(1);
final long now4 = System.currentTimeMillis();
- doReturn(new Pair<Long, Object>(now4, Integer.valueOf(0)))
+ doReturn(null)
.when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
.remove(anyInt(), anyInt());
- doReturn(new Pair<Long, Object>(now4, null))
+ doReturn(new Pair<Long, Object>(now4, Long.valueOf(lmkd_reported_rss)))
.when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
.remove(anyInt(), anyInt());
@@ -490,7 +494,7 @@
null, // subReason
0, // status
app2Pss1, // pss
- app2Rss1, // rss
+ lmkd_reported_rss, // rss
IMPORTANCE_CACHED, // importance
null); // description
@@ -499,6 +503,11 @@
mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, 0, list);
assertEquals(1, list.size());
+ info = list.get(0);
+
+ // Verify the AppExitInfo has the LMKD reported RSS
+ assertEquals(lmkd_reported_rss, info.getRss());
+
// Case 5: App native crash
final int app3UidUser2 = 1010345;
final int app3PidUser2 = 12349;
@@ -599,7 +608,7 @@
null, // subReason
0, // status
app2Pss1, // pss
- app2Rss1, // rss
+ lmkd_reported_rss, // rss
IMPORTANCE_CACHED, // importance
null); // description
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java
new file mode 100644
index 0000000..8d2849b
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerStatsProcessorTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.power.stats;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+import android.os.Handler;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.ScreenPowerStatsCollector.Injector;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.IntSupplier;
+
+public class AmbientDisplayPowerStatsProcessorTest {
+
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setNumDisplays(2)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 0, 180.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 1, 360.0);
+
+ private static final double PRECISION = 0.1;
+ private static final int VOLTAGE_MV = 3500;
+
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ @Mock
+ private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever;
+
+ private final Injector mInjector = new Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return new PowerStatsUidResolver();
+ }
+
+ @Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> VOLTAGE_MV;
+ }
+
+ @Override
+ public int getDisplayCount() {
+ return 2;
+ }
+
+ @Override
+ public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() {
+ return mScreenUsageTimeRetriever;
+ }
+ };
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void processPowerStats() {
+ PowerComponentAggregatedPowerStats stats = collectAndAggregatePowerStats();
+
+ assertPowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 16.2);
+ assertPowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 5.4);
+ assertPowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_ON, 0);
+ assertPowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_ON, 0);
+ }
+
+ private PowerComponentAggregatedPowerStats collectAndAggregatePowerStats() {
+ ScreenPowerStatsProcessor screenPowerStatsProcessor =
+ new ScreenPowerStatsProcessor(mStatsRule.getPowerProfile());
+ AmbientDisplayPowerStatsProcessor ambientDisplayPowerStatsProcessor =
+ new AmbientDisplayPowerStatsProcessor();
+
+ AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_SCREEN)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN)
+ .setProcessor(screenPowerStatsProcessor);
+ config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+ BatteryConsumer.POWER_COMPONENT_SCREEN)
+ .setProcessor(ambientDisplayPowerStatsProcessor);
+
+ AggregatedPowerStats stats = new AggregatedPowerStats(config);
+
+ stats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0);
+ stats.setDeviceState(STATE_SCREEN, SCREEN_STATE_OTHER, 0);
+
+ ScreenPowerStatsCollector collector = new ScreenPowerStatsCollector(mInjector);
+ collector.setEnabled(true);
+
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY))
+ .thenReturn(new int[0]);
+
+ stats.addPowerStats(collector.collectStats(), 1000);
+
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0))
+ .thenReturn(60_000L);
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(1))
+ .thenReturn(120_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(0))
+ .thenReturn(180_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(1))
+ .thenReturn(240_000L);
+ stats.setDeviceState(STATE_POWER, POWER_STATE_BATTERY, 101_000);
+ stats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 401_000);
+
+ // Slightly larger than 600_000 total screen time, to simulate a sight race
+ // between state changes and power stats collection
+ stats.addPowerStats(collector.collectStats(), 612_000);
+
+ stats.finish(612_000);
+
+ return stats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY);
+ }
+
+ private void assertPowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats,
+ int powerState, int screenState, double expectedPowerEstimate) {
+ PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor();
+ PowerStatsLayout layout = new PowerStatsLayout(descriptor);
+ long[] stats = new long[descriptor.statsArrayLength];
+ aggregatedStats.getDeviceStats(stats, new int[]{powerState, screenState});
+ assertThat(layout.getDevicePowerEstimate(stats)).isWithin(PRECISION)
+ .of(expectedPowerEstimate);
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.java
new file mode 100644
index 0000000..817fdcb
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsCollectorTest.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.server.power.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.ScreenPowerStatsCollector.Injector;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.IntSupplier;
+
+public class ScreenPowerStatsCollectorTest {
+ private static final int APP_UID1 = 42;
+ private static final int APP_UID2 = 24;
+ private static final int ISOLATED_UID = 99123;
+
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, 1000);
+
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ @Mock
+ private PowerStatsUidResolver mPowerStatsUidResolver;
+ @Mock
+ private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever;
+
+ private final Injector mInjector = new Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return mPowerStatsUidResolver;
+ }
+
+ @Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> 3500;
+ }
+
+ @Override
+ public int getDisplayCount() {
+ return 2;
+ }
+
+ @Override
+ public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() {
+ return mScreenUsageTimeRetriever;
+ }
+ };
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mPowerStatsUidResolver.mapUid(anyInt())).thenAnswer(invocation -> {
+ int uid = invocation.getArgument(0);
+ if (uid == ISOLATED_UID) {
+ return APP_UID2;
+ } else {
+ return uid;
+ }
+ });
+ }
+
+ @Test
+ public void collectStats() {
+ ScreenPowerStatsCollector collector = new ScreenPowerStatsCollector(mInjector);
+ collector.setEnabled(true);
+
+ // Establish a baseline
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY))
+ .thenReturn(new int[]{77});
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
+ .thenReturn(new long[]{10_000});
+
+ doAnswer(inv -> {
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback =
+ inv.getArgument(0);
+ callback.onUidTopActivityTime(APP_UID1, 1000);
+ callback.onUidTopActivityTime(APP_UID2, 2000);
+ return null;
+ }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any(
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class));
+
+ collector.collectStats();
+
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
+ .thenReturn(new long[]{45_000});
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0))
+ .thenReturn(60_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_DARK))
+ .thenReturn(10_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_MEDIUM))
+ .thenReturn(20_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_BRIGHT))
+ .thenReturn(30_000L);
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(1))
+ .thenReturn(120_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(0))
+ .thenReturn(180_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(1))
+ .thenReturn(240_000L);
+ doAnswer(inv -> {
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback =
+ inv.getArgument(0);
+ callback.onUidTopActivityTime(APP_UID1, 3000);
+ callback.onUidTopActivityTime(APP_UID2, 5000);
+ callback.onUidTopActivityTime(ISOLATED_UID, 7000);
+ return null;
+ }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any(
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class));
+
+
+ PowerStats powerStats = collector.collectStats();
+
+ ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout();
+ layout.fromExtras(powerStats.descriptor.extras);
+
+ // (45000 - 10000) / 3500
+ assertThat(layout.getConsumedEnergy(powerStats.stats, 0))
+ .isEqualTo(10_000);
+
+ assertThat(layout.getScreenOnDuration(powerStats.stats, 0))
+ .isEqualTo(60_000);
+ assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0,
+ BatteryStats.SCREEN_BRIGHTNESS_DARK))
+ .isEqualTo(10_000);
+ assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0,
+ BatteryStats.SCREEN_BRIGHTNESS_MEDIUM))
+ .isEqualTo(20_000);
+ assertThat(layout.getBrightnessLevelDuration(powerStats.stats, 0,
+ BatteryStats.SCREEN_BRIGHTNESS_BRIGHT))
+ .isEqualTo(30_000);
+ assertThat(layout.getScreenOnDuration(powerStats.stats, 1))
+ .isEqualTo(120_000);
+ assertThat(layout.getScreenDozeDuration(powerStats.stats, 0))
+ .isEqualTo(180_000);
+ assertThat(layout.getScreenDozeDuration(powerStats.stats, 1))
+ .isEqualTo(240_000);
+
+ assertThat(powerStats.uidStats.size()).isEqualTo(2);
+ // 3000 - 1000
+ assertThat(layout.getUidTopActivityDuration(powerStats.uidStats.get(APP_UID1)))
+ .isEqualTo(2000);
+ // (5000 - 2000) + 7000
+ assertThat(layout.getUidTopActivityDuration(powerStats.uidStats.get(APP_UID2)))
+ .isEqualTo(10000);
+ }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java
new file mode 100644
index 0000000..9fde61a
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerStatsProcessorTest.java
@@ -0,0 +1,287 @@
+/*
+ * 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.power.stats;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_BATTERY;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+import com.android.server.power.stats.ScreenPowerStatsCollector.Injector;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.IntSupplier;
+
+public class ScreenPowerStatsProcessorTest {
+
+ @Rule(order = 0)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProvideMainThread(true)
+ .build();
+
+ @Rule(order = 1)
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+ .setNumDisplays(2)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 0, 180.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_AMBIENT, 1, 360.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON, 0, 480.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON, 1, 720.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL, 0, 4800.0)
+ .setAveragePowerForOrdinal(PowerProfile.POWER_GROUP_DISPLAY_SCREEN_ON, 1, 7200.0)
+ .initMeasuredEnergyStatsLocked();
+
+ private static final double PRECISION = 0.1;
+ private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+ private static final int VOLTAGE_MV = 3500;
+
+ @Mock
+ private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+ @Mock
+ private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever;
+
+ private final Injector mInjector = new Injector() {
+ @Override
+ public Handler getHandler() {
+ return mStatsRule.getHandler();
+ }
+
+ @Override
+ public Clock getClock() {
+ return mStatsRule.getMockClock();
+ }
+
+ @Override
+ public PowerStatsUidResolver getUidResolver() {
+ return new PowerStatsUidResolver();
+ }
+
+ @Override
+ public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+ return 0;
+ }
+
+ @Override
+ public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+ return mConsumedEnergyRetriever;
+ }
+
+ @Override
+ public IntSupplier getVoltageSupplier() {
+ return () -> VOLTAGE_MV;
+ }
+
+ @Override
+ public int getDisplayCount() {
+ return 2;
+ }
+
+ @Override
+ public ScreenPowerStatsCollector.ScreenUsageTimeRetriever getScreenUsageTimeRetriever() {
+ return mScreenUsageTimeRetriever;
+ }
+ };
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void processPowerStats_powerProfile() {
+ PowerComponentAggregatedPowerStats stats = collectAndAggregatePowerStats(false);
+
+ assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_ON, 195.5, 0);
+ assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0, 0.6);
+ assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_ON, 97.8, 0);
+ assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0, 0);
+
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_ON, 78.2);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_ON, 39.1);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0);
+
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_ON, 117.3);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_ON, 58.7);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0);
+ }
+
+ @Test
+ public void processPowerStats_energyConsumer() {
+ PowerComponentAggregatedPowerStats stats = collectAndAggregatePowerStats(true);
+
+ assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_ON, 261.9, 0);
+ assertDevicePowerEstimate(stats, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0, 7.2);
+ assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_ON, 130.9, 0);
+ assertDevicePowerEstimate(stats, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0, 0);
+
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_ON, 104.8);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_ON, 52.4);
+ assertUidPowerEstimate(stats, APP_UID1, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0);
+
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_ON, 157.1);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_BATTERY, SCREEN_STATE_OTHER, 0);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_ON, 78.6);
+ assertUidPowerEstimate(stats, APP_UID2, POWER_STATE_OTHER, SCREEN_STATE_OTHER, 0);
+ }
+
+ private PowerComponentAggregatedPowerStats collectAndAggregatePowerStats(
+ boolean energyConsumer) {
+ ScreenPowerStatsProcessor processor =
+ new ScreenPowerStatsProcessor(mStatsRule.getPowerProfile());
+
+ PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
+
+ ScreenPowerStatsCollector collector = new ScreenPowerStatsCollector(mInjector);
+ collector.setEnabled(true);
+
+ if (energyConsumer) {
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY))
+ .thenReturn(new int[]{77});
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
+ .thenReturn(new long[]{10_000});
+ } else {
+ when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.DISPLAY))
+ .thenReturn(new int[0]);
+ }
+
+ doAnswer(inv -> {
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback =
+ inv.getArgument(0);
+ callback.onUidTopActivityTime(APP_UID1, 1000);
+ callback.onUidTopActivityTime(APP_UID2, 2000);
+ return null;
+ }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any(
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class));
+
+ aggregatedStats.addPowerStats(collector.collectStats(), 1000);
+
+ if (energyConsumer) {
+ // 400 mAh represented as microWattSeconds
+ long energyUws = 400L * 3600 * VOLTAGE_MV;
+ when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{77}))
+ .thenReturn(new long[]{10_000 + energyUws});
+ }
+
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(0))
+ .thenReturn(60_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_DARK))
+ .thenReturn(10_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_MEDIUM))
+ .thenReturn(20_000L);
+ when(mScreenUsageTimeRetriever.getBrightnessLevelTimeMs(0,
+ BatteryStats.SCREEN_BRIGHTNESS_BRIGHT))
+ .thenReturn(30_000L);
+ when(mScreenUsageTimeRetriever.getScreenOnTimeMs(1))
+ .thenReturn(120_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(0))
+ .thenReturn(180_000L);
+ when(mScreenUsageTimeRetriever.getScreenDozeTimeMs(1))
+ .thenReturn(240_000L);
+ doAnswer(inv -> {
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback callback =
+ inv.getArgument(0);
+ callback.onUidTopActivityTime(APP_UID1, 3000);
+ callback.onUidTopActivityTime(APP_UID2, 5000);
+ return null;
+ }).when(mScreenUsageTimeRetriever).retrieveTopActivityTimes(any(
+ ScreenPowerStatsCollector.ScreenUsageTimeRetriever.Callback.class));
+
+ aggregatedStats.setState(STATE_POWER, POWER_STATE_BATTERY, 201_000);
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 601_000);
+
+ // Slightly larger than 600_000 total screen time, to simulate a sight race
+ // between state changes and power stats collection
+ aggregatedStats.addPowerStats(collector.collectStats(), 612_000);
+
+ aggregatedStats.getConfig().getProcessor().finish(aggregatedStats, 180_000);
+ return aggregatedStats;
+ }
+
+ private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
+ ScreenPowerStatsProcessor processor) {
+ AggregatedPowerStatsConfig.PowerComponent config =
+ new AggregatedPowerStatsConfig.PowerComponent(
+ BatteryConsumer.POWER_COMPONENT_SCREEN)
+ .trackDeviceStates(STATE_POWER, STATE_SCREEN)
+ .trackUidStates(STATE_POWER, STATE_SCREEN)
+ .setProcessor(processor);
+
+ PowerComponentAggregatedPowerStats aggregatedStats =
+ new PowerComponentAggregatedPowerStats(
+ new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config);
+
+ aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+ aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+
+ return aggregatedStats;
+ }
+
+ private void assertDevicePowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats,
+ int powerState, int screenState, double expectedScreenPowerEstimate,
+ double expectedDozePowerEstimate) {
+ PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor();
+ ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout(descriptor);
+ long[] stats = new long[descriptor.statsArrayLength];
+ aggregatedStats.getDeviceStats(stats, new int[]{powerState, screenState});
+ assertThat(layout.getDevicePowerEstimate(stats)).isWithin(PRECISION)
+ .of(expectedScreenPowerEstimate);
+ assertThat(layout.getScreenDozePowerEstimate(stats)).isWithin(PRECISION)
+ .of(expectedDozePowerEstimate);
+ }
+
+ private void assertUidPowerEstimate(PowerComponentAggregatedPowerStats aggregatedStats, int uid,
+ int powerState, int screenState, double expectedScreenPowerEstimate) {
+ PowerStats.Descriptor descriptor = aggregatedStats.getPowerStatsDescriptor();
+ ScreenPowerStatsLayout layout = new ScreenPowerStatsLayout(descriptor);
+ long[] stats = new long[descriptor.uidStatsArrayLength];
+ aggregatedStats.getUidStats(stats, uid,
+ new int[]{powerState, screenState, PROCESS_STATE_ANY});
+ assertThat(layout.getUidPowerEstimate(stats)).isWithin(PRECISION)
+ .of(expectedScreenPowerEstimate);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
index f92eaab..c1b3929 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerManagerTest.java
@@ -18,13 +18,13 @@
import static com.android.server.accessibility.Flags.FLAG_ENABLE_A11Y_CHECKER_LOGGING;
import static com.android.server.accessibility.a11ychecker.AccessibilityCheckerConstants.MIN_DURATION_BETWEEN_CHECKS;
+import static com.android.server.accessibility.a11ychecker.TestUtils.QUALIFIED_TEST_ACTIVITY_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_DEFAULT_BROWSER;
import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom;
import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
-import static com.android.server.accessibility.a11ychecker.TestUtils.getTestAccessibilityEvent;
import static com.google.common.truth.Truth.assertThat;
@@ -114,7 +114,7 @@
Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
mAccessibilityCheckerManager.maybeRunA11yChecker(
- List.of(mockNodeInfo1, mockNodeInfo2), getTestAccessibilityEvent(),
+ List.of(mockNodeInfo1, mockNodeInfo2), QUALIFIED_TEST_ACTIVITY_NAME,
new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
@@ -139,7 +139,7 @@
Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
mAccessibilityCheckerManager.maybeRunA11yChecker(
- List.of(mockNodeInfo), getTestAccessibilityEvent(),
+ List.of(mockNodeInfo), QUALIFIED_TEST_ACTIVITY_NAME,
new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
@@ -160,7 +160,7 @@
Set<A11yCheckerProto.AccessibilityCheckResultReported> results =
mAccessibilityCheckerManager.maybeRunA11yChecker(
- List.of(mockNodeInfo, mockNodeInfoDuplicate), getTestAccessibilityEvent(),
+ List.of(mockNodeInfo, mockNodeInfoDuplicate), QUALIFIED_TEST_ACTIVITY_NAME,
new ComponentName(TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME,
TEST_A11Y_SERVICE_CLASS_NAME), /*userId=*/ 0);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
index 141f174..5b4e72e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/AccessibilityCheckerUtilsTest.java
@@ -16,11 +16,13 @@
package com.android.server.accessibility.a11ychecker;
+import static com.android.server.accessibility.a11ychecker.TestUtils.QUALIFIED_TEST_ACTIVITY_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_CLASS_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_A11Y_SERVICE_SOURCE_PACKAGE_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_ACTIVITY_NAME;
+import static com.android.server.accessibility.a11ychecker.TestUtils.TEST_APP_PACKAGE_NAME;
import static com.android.server.accessibility.a11ychecker.TestUtils.createAtom;
import static com.android.server.accessibility.a11ychecker.TestUtils.getMockPackageManagerWithInstalledApps;
-import static com.android.server.accessibility.a11ychecker.TestUtils.getTestAccessibilityEvent;
import static com.google.common.truth.Truth.assertThat;
@@ -28,7 +30,6 @@
import android.content.ComponentName;
import android.content.pm.PackageManager;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.runner.AndroidJUnit4;
@@ -138,11 +139,15 @@
}
@Test
- public void getActivityName_hasWindowStateChangedEvent_returnsActivityName() {
- AccessibilityEvent accessibilityEvent = getTestAccessibilityEvent();
-
+ public void getActivityName_hasValidActivityClassName_returnsActivityName() {
assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager,
- accessibilityEvent)).isEqualTo("MainActivity");
+ TEST_APP_PACKAGE_NAME, QUALIFIED_TEST_ACTIVITY_NAME)).isEqualTo(TEST_ACTIVITY_NAME);
+ }
+
+ @Test
+ public void getActivityName_hasInvalidActivityClassName_returnsActivityName() {
+ assertThat(AccessibilityCheckerUtils.getActivityName(mMockPackageManager,
+ TEST_APP_PACKAGE_NAME, "com.NonActivityClass")).isEmpty();
}
// Makes sure the AccessibilityHierarchyCheck class to enum mapping is up to date with the
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
index ec1a255..acf64b6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/a11ychecker/TestUtils.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility.a11ychecker;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.when;
@@ -26,6 +27,7 @@
import android.content.pm.PackageManager;
import android.view.accessibility.AccessibilityEvent;
+import org.mockito.AdditionalMatchers;
import org.mockito.Mockito;
public class TestUtils {
@@ -46,8 +48,11 @@
ComponentName testActivityComponentName = new ComponentName(TEST_APP_PACKAGE_NAME,
QUALIFIED_TEST_ACTIVITY_NAME);
- when(mockPackageManager.getActivityInfo(testActivityComponentName, 0))
+ when(mockPackageManager.getActivityInfo(eq(testActivityComponentName), eq(0)))
.thenReturn(testActivityInfo);
+ when(mockPackageManager.getActivityInfo(
+ AdditionalMatchers.not(eq(testActivityComponentName)), eq(0)))
+ .thenThrow(PackageManager.NameNotFoundException.class);
when(mockPackageManager.getPackageInfo(TEST_APP_PACKAGE_NAME, 0))
.thenReturn(createPackageInfo(TEST_APP_PACKAGE_NAME, TEST_APP_VERSION_CODE,
testActivityInfo));
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java b/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
index f3440f7..ea3b409 100644
--- a/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/location/ZoneInfoDbTimeZoneProviderEventPreProcessorTest.java
@@ -39,13 +39,23 @@
private static final long ARBITRARY_TIME_MILLIS = 11223344;
+ private final List<String> mNonExistingTimeZones = Arrays.asList(
+ "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30");
private final ZoneInfoDbTimeZoneProviderEventPreProcessor mPreProcessor =
new ZoneInfoDbTimeZoneProviderEventPreProcessor();
+ private static final TimeZoneProviderStatus ARBITRARY_TIME_ZONE_PROVIDER_STATUS =
+ new TimeZoneProviderStatus.Builder()
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+ .build();
+
@Test
public void timeZoneIdsFromZoneInfoDbAreValid() {
for (String timeZone : TimeZone.getAvailableIDs()) {
- TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+ TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone,
+ ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
assertWithMessage("Time zone %s should be supported", timeZone)
.that(mPreProcessor.preProcess(event)).isEqualTo(event);
}
@@ -53,11 +63,9 @@
@Test
public void eventWithNonExistingZones_areMappedToUncertainEvent() {
- List<String> nonExistingTimeZones = Arrays.asList(
- "SystemV/HST10", "Atlantic/Atlantis", "EUROPE/LONDON", "Etc/GMT-5:30");
-
- for (String timeZone : nonExistingTimeZones) {
- TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone);
+ for (String timeZone : mNonExistingTimeZones) {
+ TimeZoneProviderEvent event = timeZoneProviderEvent(timeZone,
+ ARBITRARY_TIME_ZONE_PROVIDER_STATUS);
TimeZoneProviderStatus expectedProviderStatus =
new TimeZoneProviderStatus.Builder(event.getTimeZoneProviderStatus())
@@ -73,14 +81,31 @@
}
}
- private static TimeZoneProviderEvent timeZoneProviderEvent(String... timeZoneIds) {
- TimeZoneProviderStatus providerStatus = new TimeZoneProviderStatus.Builder()
- .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
- .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
- .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
- .build();
+ @Test
+ public void eventWithNullProviderStatus_areMappedToUncertainEvent() {
+ for (String timeZone : mNonExistingTimeZones) {
+ TimeZoneProviderEvent eventWithNullStatus = timeZoneProviderEvent(timeZone,
+ /* providerStatus= */ null);
+
+ TimeZoneProviderStatus expectedProviderStatus =
+ new TimeZoneProviderStatus.Builder()
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_FAILED)
+ .build();
+
+ TimeZoneProviderEvent expectedResultEvent =
+ TimeZoneProviderEvent.createUncertainEvent(
+ eventWithNullStatus.getCreationElapsedMillis(),
+ expectedProviderStatus);
+ assertWithMessage(timeZone + " with null time zone provider status")
+ .that(mPreProcessor.preProcess(eventWithNullStatus))
+ .isEqualTo(expectedResultEvent);
+ }
+ }
+
+ private static TimeZoneProviderEvent timeZoneProviderEvent(String timeZoneId,
+ TimeZoneProviderStatus providerStatus) {
TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
- .setTimeZoneIds(Arrays.asList(timeZoneIds))
+ .setTimeZoneIds(Arrays.asList(timeZoneId))
.setElapsedRealtimeMillis(ARBITRARY_TIME_MILLIS)
.build();
return TimeZoneProviderEvent.createSuggestionEvent(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index b07940a..d7bae45 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1044,6 +1044,8 @@
AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
mZenModeHelper.mAudioManager = mAudioManager;
setupZenConfig();
+ mTestableLooper.processAllMessages();
+ reset(mAudioManager);
// Turn manual zen mode on
mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP,
@@ -1063,6 +1065,44 @@
}
@Test
+ public void testSetConfig_updatesAudioForSequentialChangesToZenMode() {
+ AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
+ mZenModeHelper.mAudioManager = mAudioManager;
+ setupZenConfig();
+ mTestableLooper.processAllMessages();
+ reset(mAudioManager);
+
+ // Turn manual zen mode on
+ mZenModeHelper.setManualZenMode(
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ null,
+ UPDATE_ORIGIN_APP,
+ null,
+ "test",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.setManualZenMode(
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ null,
+ UPDATE_ORIGIN_APP,
+ null,
+ "test",
+ CUSTOM_PKG_UID);
+
+ // audio manager shouldn't do anything until the handler processes its messages
+ verify(mAudioManager, never()).updateRingerModeAffectedStreamsInternal();
+
+ // now process the looper's messages
+ mTestableLooper.processAllMessages();
+
+ // Expect calls to audio manager
+ verify(mAudioManager, times(2)).updateRingerModeAffectedStreamsInternal();
+ verify(mAudioManager, times(1)).setRingerModeInternal(anyInt(), anyString());
+
+ // called during applyZenToRingerMode(), which should be true since zen changed
+ verify(mAudioManager, atLeastOnce()).getRingerModeInternal();
+ }
+
+ @Test
public void testParcelConfig() {
mZenModeHelper.setNotificationPolicy(new Policy(PRIORITY_CATEGORY_EVENTS
| PRIORITY_CATEGORY_MESSAGES | PRIORITY_CATEGORY_REPEAT_CALLERS
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 8ca8623..c496bbb 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -45,6 +45,11 @@
import android.os.IBinder;
import android.os.Process;
import android.os.test.TestLooper;
+import android.os.vibrator.Flags;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -63,7 +68,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
public class VibratorControlServiceTest {
@@ -71,6 +75,8 @@
@Rule
public MockitoRule rule = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock private VibrationScaler mMockVibrationScaler;
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@@ -98,6 +104,7 @@
mVibratorControlService = new VibratorControlService(
InstrumentationRegistry.getContext(), new VibratorControllerHolder(),
mMockVibrationScaler, mVibrationSettings, mStatsLoggerMock, mLock);
+ mFakeVibratorController.setVibratorControlService(mVibratorControlService);
}
@Test
@@ -280,10 +287,10 @@
CompletableFuture<Void> future =
mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
timeoutInMillis);
- try {
- future.orTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).get();
- } catch (Throwable ignored) {
- }
+ mTestLooper.dispatchAll();
+
+ assertThat(future).isNotNull();
+ assertThat(future.isDone()).isTrue();
assertThat(mFakeVibratorController.didRequestVibrationParams).isTrue();
assertThat(mFakeVibratorController.requestVibrationType).isEqualTo(
ScaleParam.TYPE_RINGTONE);
@@ -315,6 +322,46 @@
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_THROTTLE_VIBRATION_PARAMS_REQUESTS)
+ public void testRequestVibrationParams_withOngoingRequestAndSameUsage_returnOngoingFuture() {
+ int timeoutInMillis = 10;
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ CompletableFuture<Void> future =
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+ timeoutInMillis);
+ CompletableFuture<Void> future2 =
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+ timeoutInMillis);
+ mTestLooper.dispatchAll();
+
+ assertThat(future).isNotNull();
+ assertThat(future).isEqualTo(future2);
+ assertThat(future.isDone()).isTrue();
+ assertThat(mFakeVibratorController.requestVibrationParamsCounter).isEqualTo(1);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_THROTTLE_VIBRATION_PARAMS_REQUESTS)
+ public void testRequestVibrationParams_withOngoingRequestAndSameUsage_returnNewFuture() {
+ int timeoutInMillis = 10;
+ mVibratorControlService.registerVibratorController(mFakeVibratorController);
+ CompletableFuture<Void> future =
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+ timeoutInMillis);
+ CompletableFuture<Void> future2 =
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
+ timeoutInMillis);
+ mTestLooper.dispatchAll();
+
+ assertThat(future).isNotNull();
+ assertThat(future2).isNotNull();
+ assertThat(future).isNotEqualTo(future2);
+ assertThat(future.isDone()).isTrue();
+ assertThat(future2.isDone()).isTrue();
+ assertThat(mFakeVibratorController.requestVibrationParamsCounter).isEqualTo(2);
+ }
+
private static int buildVibrationTypesMask(int... types) {
int typesMask = 0;
for (int type : types) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 1f4a469..bea6917 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1603,7 +1603,7 @@
@Test
@RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
public void vibrate_vendorEffectsWithPermission_successful() throws Exception {
- // Deny permission to vibrate with vendor effects
+ // Grant permission to vibrate with vendor effects
grantPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS);
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
@@ -1767,6 +1767,9 @@
})
public void vibrate_withIntensitySettingsAndAdaptiveHaptics_appliesSettingsToVendorEffects()
throws Exception {
+ // Grant permission to vibrate with vendor effects
+ grantPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS);
+
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
Vibrator.VIBRATION_INTENSITY_LOW);
@@ -1789,7 +1792,7 @@
assertThat(fakeVibrator.getAllVendorEffects()).hasSize(1);
VibrationEffect.VendorEffect scaled = fakeVibrator.getAllVendorEffects().get(0);
- assertThat(scaled.getEffectStrength()).isEqualTo(VibrationEffect.EFFECT_STRENGTH_STRONG);
+ assertThat(scaled.getEffectStrength()).isEqualTo(VibrationEffect.EFFECT_STRENGTH_LIGHT);
assertThat(scaled.getLinearScale()).isEqualTo(0.4f);
}
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
index 0cd88ef..c0e1407 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
@@ -39,6 +39,7 @@
public boolean didRequestVibrationParams = false;
public int requestVibrationType = VibrationAttributes.USAGE_UNKNOWN;
public long requestTimeoutInMillis = 0;
+ public int requestVibrationParamsCounter = 0;
public FakeVibratorController(Looper looper) {
mHandler = new Handler(looper);
@@ -58,6 +59,7 @@
didRequestVibrationParams = true;
requestVibrationType = vibrationType;
requestTimeoutInMillis = timeoutInMillis;
+ requestVibrationParamsCounter++;
mHandler.post(() -> {
if (mVibratorControlService != null) {
mVibratorControlService.onRequestVibrationParamsComplete(token, mRequestResult);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 11f7560..220248c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -126,6 +126,10 @@
.isCameraActive(any(ActivityRecord.class), anyBoolean());
}
+ void setDisplayNaturalOrientation(@Configuration.Orientation int naturalOrientation) {
+ doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
+ }
+
@NonNull
ActivityRecord top() {
return mActivityStack.top();
@@ -189,6 +193,11 @@
mDisplayContent.setIgnoreOrientationRequest(enabled);
}
+ void setTopTaskInMultiWindowMode(boolean inMultiWindowMode) {
+ doReturn(inMultiWindowMode).when(mTaskStack.top())
+ .inMultiWindowMode();
+ }
+
void setTopActivityAsEmbedded(boolean embedded) {
doReturn(embedded).when(mActivityStack.top()).isEmbedded();
}
@@ -225,20 +234,21 @@
void createNewDisplay() {
mDisplayContent = new TestDisplayContent.Builder(mAtm, mDisplayWidth, mDisplayHeight)
.build();
+ spyOn(mDisplayContent);
spyOnAppCompatCameraPolicy();
}
void createNewTask() {
final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
.setDisplay(mDisplayContent).build();
- mTaskStack.push(newTask);
+ pushTask(newTask);
}
void createNewTaskWithBaseActivity() {
final Task newTask = new WindowTestsBase.TaskBuilder(mSupervisor)
.setCreateActivity(true)
.setDisplay(mDisplayContent).build();
- mTaskStack.push(newTask);
+ pushTask(newTask);
pushActivity(newTask.getTopNonFinishingActivity());
}
@@ -430,9 +440,15 @@
spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
spyOn(activity.mAppCompatController.getAppCompatFocusOverrides());
+ spyOn(activity.mAppCompatController.getAppCompatResizeOverrides());
spyOn(activity.mLetterboxUiController);
}
+ private void pushTask(@NonNull Task task) {
+ spyOn(task);
+ mTaskStack.push(task);
+ }
+
private void spyOnAppCompatCameraPolicy() {
spyOn(mDisplayContent.mAppCompatCameraPolicy);
if (mDisplayContent.mAppCompatCameraPolicy.hasDisplayRotationCompatPolicy()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index 634453f..6c0d8c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
+import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -158,6 +162,72 @@
});
}
+ @Test
+ @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+ public void testShouldUseDisplayLandscapeNaturalOrientation_override_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setDisplayNaturalOrientation(ORIENTATION_LANDSCAPE);
+ a.setIgnoreOrientationRequest(true);
+ a.createActivityWithComponent();
+ });
+ robot.checkShouldUseDisplayLandscapeNaturalOrientation(/* expected */ true);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+ public void testShouldUseDisplayLandscapeNaturalOrientation_falseProperty_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE);
+ robot.applyOnActivity((a) -> {
+ a.setDisplayNaturalOrientation(ORIENTATION_LANDSCAPE);
+ a.setIgnoreOrientationRequest(true);
+ a.createActivityWithComponent();
+ });
+ robot.checkShouldUseDisplayLandscapeNaturalOrientation(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+ public void testShouldUseDisplayLandscapeNaturalOrientation_portrait_isFalse() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setDisplayNaturalOrientation(ORIENTATION_PORTRAIT);
+ a.setIgnoreOrientationRequest(true);
+ a.createActivityWithComponent();
+ });
+ robot.checkShouldUseDisplayLandscapeNaturalOrientation(/* expected */ false);
+ });
+ }
+ @Test
+ @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+ public void testShouldUseDisplayLandscapeNaturalOrientation_noIgnoreRequest_isFalse() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setDisplayNaturalOrientation(ORIENTATION_LANDSCAPE);
+ a.setIgnoreOrientationRequest(false);
+ a.createActivityWithComponent();
+ });
+ robot.checkShouldUseDisplayLandscapeNaturalOrientation(/* expected */ false);
+ });
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
+ public void testShouldUseDisplayLandscapeNaturalOrientation_inMultiWindowMode_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.applyOnActivity((a) -> {
+ a.setDisplayNaturalOrientation(ORIENTATION_LANDSCAPE);
+ a.setIgnoreOrientationRequest(true);
+ a.createActivityWithComponent();
+ a.setTopTaskInMultiWindowMode(/* inMultiWindowMode */ true);
+ });
+ robot.checkShouldUseDisplayLandscapeNaturalOrientation(/* expected */ false);
+ });
+ }
+
/**
* Runs a test scenario providing a Robot.
*/
@@ -215,6 +285,11 @@
}
}
+ void checkShouldUseDisplayLandscapeNaturalOrientation(boolean expected) {
+ assertEquals(expected,
+ getTopOrientationOverrides().shouldUseDisplayLandscapeNaturalOrientation());
+ }
+
private AppCompatOrientationOverrides getTopOrientationOverrides() {
return activity().top().mAppCompatController.getAppCompatOverrides()
.getAppCompatOrientationOverrides();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
new file mode 100644
index 0000000..8fc1a77
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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.wm;
+
+import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
+import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import libcore.junit.util.compat.CoreCompatChangeRule;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link AppCompatResizeOverrides}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:AppCompatResizeOverridesTest
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class AppCompatResizeOverridesTest extends WindowTestsBase {
+
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_overrideEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ true);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_propertyTrue_overrideEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ true);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_propertyTrue_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_propertyFalse_overrideEnabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_RESIZE_APP})
+ public void testShouldOverrideForceResizeApp_propertyFalse_noOverride_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceResizeApp(/* expected */ false);
+ });
+ }
+
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_overrideEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ true);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_propertyTrue_overrideEnabled_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ true);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_propertyTrue_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().enable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_overrideDisabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.EnableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_propertyFalse_overrideEnabled_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ false);
+ });
+ }
+
+ @Test
+ @CoreCompatChangeRule.DisableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testShouldOverrideForceNonResizeApp_propertyFalse_noOverride_returnsFalse() {
+ runTestScenario((robot) -> {
+ robot.prop().disable(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ robot.activity().createActivityWithComponent();
+ robot.checkShouldOverrideForceNonResizeApp(/* expected */ false);
+ });
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ void runTestScenario(@NonNull Consumer<ResizeOverridesRobotTest> consumer) {
+ spyOn(mWm.mAppCompatConfiguration);
+ final ResizeOverridesRobotTest robot = new ResizeOverridesRobotTest(mWm, mAtm, mSupervisor);
+ consumer.accept(robot);
+ }
+
+ private static class ResizeOverridesRobotTest extends AppCompatRobotBase {
+
+ ResizeOverridesRobotTest(@NonNull WindowManagerService wm,
+ @NonNull ActivityTaskManagerService atm,
+ @NonNull ActivityTaskSupervisor supervisor) {
+ super(wm, atm, supervisor);
+ }
+
+
+ void checkShouldOverrideForceResizeApp(boolean expected) {
+ Assert.assertEquals(expected, activity().top().mAppCompatController
+ .getAppCompatResizeOverrides().shouldOverrideForceResizeApp());
+ }
+
+ void checkShouldOverrideForceNonResizeApp(boolean expected) {
+ Assert.assertEquals(expected, activity().top().mAppCompatController
+ .getAppCompatResizeOverrides().shouldOverrideForceNonResizeApp());
+ }
+ }
+
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 8cdb574..1072ef0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -152,8 +152,8 @@
// Use a new TestIWindow so we don't collect events for other windows
final WindowState window = createWindow(
null, TYPE_BASE_APPLICATION, activity, name, ownerId, false, new TestIWindow());
- window.mInputChannel = new InputChannel();
- window.mInputChannelToken = window.mInputChannel.getToken();
+ InputChannel channel = new InputChannel();
+ window.openInputChannel(channel);
window.mHasSurface = true;
mWm.mWindowMap.put(window.mClient.asBinder(), window);
mWm.mInputToWindowMap.put(window.mInputChannelToken, window);
@@ -178,8 +178,8 @@
TEST_PID, TEST_UID);
mWindow = createDropTargetWindow("Drag test window", 0);
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
- when(mWm.mInputManager.startDragAndDrop(any(InputChannel.class),
- any(InputChannel.class))).thenReturn(true);
+ when(mWm.mInputManager.startDragAndDrop(any(IBinder.class),
+ any(IBinder.class))).thenReturn(true);
mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
}
@@ -707,8 +707,7 @@
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- assertTrue(mWm.mInputManager.startDragAndDrop(new InputChannel(),
- new InputChannel()));
+ assertTrue(mWm.mInputManager.startDragAndDrop(new Binder(), new Binder()));
mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient,
flag, surface, 0, 0, 0, 0, 0, 0, 0, data);
assertNotNull(mToken);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index a0a2904..e2c0f6c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -16,14 +16,7 @@
package com.android.server.wm;
-import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
-import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
-import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
@@ -36,15 +29,12 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.Property;
import android.content.res.Resources;
import android.graphics.Rect;
import android.platform.test.annotations.DisableFlags;
@@ -62,9 +52,6 @@
import com.android.internal.R;
import com.android.window.flags.Flags;
-import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
-import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -309,172 +296,6 @@
return mainWindow;
}
- // shouldUseDisplayLandscapeNaturalOrientation
-
- @Test
- @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
- public void testShouldUseDisplayLandscapeNaturalOrientation_override_returnsTrue() {
- prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
- assertTrue(mController.shouldUseDisplayLandscapeNaturalOrientation());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
- public void testShouldUseDisplayLandscapeNaturalOrientation_overrideAndFalseProperty_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE, /* value */ false);
-
- mController = new LetterboxUiController(mWm, mActivity);
-
- prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
- assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
- public void testShouldUseDisplayLandscapeNaturalOrientation_portraitNaturalOrientation_returnsFalse() {
- prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
- doReturn(ORIENTATION_PORTRAIT).when(mDisplayContent).getNaturalOrientation();
-
- assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
- public void testShouldUseDisplayLandscapeNaturalOrientation_disabledIgnoreOrientationRequest_returnsFalse() {
- prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
- mDisplayContent.setIgnoreOrientationRequest(false);
-
- assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
- }
-
- @Test
- @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
- public void testShouldUseDisplayLandscapeNaturalOrientation_inMultiWindowMode_returnsFalse() {
- prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation();
-
- spyOn(mTask);
- doReturn(true).when(mTask).inMultiWindowMode();
-
- assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation());
- }
-
- @Test
- @EnableCompatChanges({FORCE_RESIZE_APP})
- public void testshouldOverrideForceResizeApp_overrideEnabled_returnsTrue() {
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @EnableCompatChanges({FORCE_RESIZE_APP})
- public void testshouldOverrideForceResizeApp_propertyTrue_overrideEnabled_returnsTrue()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_RESIZE_APP})
- public void testshouldOverrideForceResizeApp_propertyTrue_overrideDisabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_RESIZE_APP})
- public void testshouldOverrideForceResizeApp_overrideDisabled_returnsFalse() {
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @EnableCompatChanges({FORCE_RESIZE_APP})
- public void testshouldOverrideForceResizeApp_propertyFalse_overrideEnabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
-
- mActivity = setUpActivityWithComponent();
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_RESIZE_APP})
- public void testshouldOverrideForceResizeApp_propertyFalse_noOverride_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceResizeApp());
- }
-
- @Test
- @EnableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_overrideEnabled_returnsTrue() {
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldOverrideForceNonResizeApp());
- }
-
- @Test
- @EnableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_propertyTrue_overrideEnabled_returnsTrue()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertTrue(mController.shouldOverrideForceNonResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_propertyTrue_overrideDisabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceNonResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_overrideDisabled_returnsFalse() {
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceNonResizeApp());
- }
-
- @Test
- @EnableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_propertyFalse_overrideEnabled_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
-
- mActivity = setUpActivityWithComponent();
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceNonResizeApp());
- }
-
- @Test
- @DisableCompatChanges({FORCE_NON_RESIZE_APP})
- public void testshouldOverrideForceNonResizeApp_propertyFalse_noOverride_returnsFalse()
- throws Exception {
- mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
- mController = new LetterboxUiController(mWm, mActivity);
-
- assertFalse(mController.shouldOverrideForceNonResizeApp());
- }
-
@Test
public void testgetFixedOrientationLetterboxAspectRatio_splitScreenAspectEnabled() {
doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
@@ -604,20 +425,6 @@
verify(mAppCompatConfiguration).getIsEducationEnabled();
}
- private void mockThatProperty(String propertyName, boolean value) throws Exception {
- Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
- /* className */ "");
- PackageManager pm = mWm.mContext.getPackageManager();
- spyOn(pm);
- doReturn(property).when(pm).getProperty(eq(propertyName), anyString());
- }
-
- private void prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation() {
- spyOn(mDisplayContent);
- doReturn(ORIENTATION_LANDSCAPE).when(mDisplayContent).getNaturalOrientation();
- mDisplayContent.setIgnoreOrientationRequest(true);
- }
-
private ActivityRecord setUpActivityWithComponent() {
mDisplayContent = new TestDisplayContent
.Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 280fe4c..34f7ebb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -66,6 +66,7 @@
import org.junit.Test;
import org.junit.rules.TestName;
+import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -131,7 +132,7 @@
assertTrue("Failed to wait for transaction to get committed",
countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
assertTrue("Failed to wait for stable geometry",
- waitForStableWindowGeometry(WAIT_TIME_S, TimeUnit.SECONDS));
+ waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIME_S)));
ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
.setCaptureSecureLayers(true)
@@ -212,7 +213,7 @@
assertTrue("Failed to wait for transaction to get committed",
countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
assertTrue("Failed to wait for stable geometry",
- waitForStableWindowGeometry(WAIT_TIME_S, TimeUnit.SECONDS));
+ waitForStableWindowGeometry(Duration.ofSeconds(WAIT_TIME_S)));
ScreenshotHardwareBuffer[] screenCapture = new ScreenshotHardwareBuffer[1];
Bitmap screenshot = null;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 72747c9..ed93a8c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -788,7 +788,7 @@
// Change the fixed orientation.
mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
assertTrue(mActivity.isRelaunching());
- assertTrue(mActivity.mLetterboxUiController
+ assertTrue(mActivity.mAppCompatController.getAppCompatOrientationOverrides()
.getIsRelaunchingAfterRequestedOrientationChanged());
assertFitted();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index d5d2847..b92af87 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -77,6 +77,7 @@
import android.view.SurfaceControl;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.internal.os.BackgroundThread;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
@@ -553,6 +554,9 @@
// This is a different handler object than the wm.mAnimationHandler above.
waitHandlerIdle(AnimationThread.getHandler());
waitHandlerIdle(SurfaceAnimationThread.getHandler());
+ // Some binder calls are posted to BackgroundThread.getHandler(), we should wait for them
+ // to finish to run next test.
+ waitHandlerIdle(BackgroundThread.getHandler());
}
static void waitHandlerIdle(Handler handler) {
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 a1ac02a..a232ff0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -46,6 +46,7 @@
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;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.google.common.truth.Truth.assertThat;
@@ -78,6 +79,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
@@ -2031,6 +2033,47 @@
task.getTaskInfo().appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode);
}
+ @Test
+ public void testUpdateTaskDescriptionOnReparent() {
+ final Task rootTask1 = createTask(mDisplayContent);
+ final Task rootTask2 = createTask(mDisplayContent);
+ final Task childTask = createTaskInRootTask(rootTask1, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, childTask);
+ final String testLabel = "test_task_description_label";
+ final ActivityManager.TaskDescription td = new ActivityManager.TaskDescription(testLabel);
+ activity.setTaskDescription(td);
+
+ // Ensure the td is set for the original root task
+ assertEquals(testLabel, rootTask1.getTaskDescription().getLabel());
+ assertNull(rootTask2.getTaskDescription().getLabel());
+
+ childTask.reparent(rootTask2, POSITION_TOP, false /* moveParents */, "reparent");
+
+ // Ensure the td is set for the new root task
+ assertEquals(testLabel, rootTask2.getTaskDescription().getLabel());
+ }
+
+ @Test
+ public void testUpdateTaskDescriptionOnReorder() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity1 = createActivityRecord(mDisplayContent, task);
+ final ActivityRecord activity2 = createActivityRecord(mDisplayContent, task);
+ final ActivityManager.TaskDescription td1 = new ActivityManager.TaskDescription();
+ td1.setBackgroundColor(Color.RED);
+ activity1.setTaskDescription(td1);
+ final ActivityManager.TaskDescription td2 = new ActivityManager.TaskDescription();
+ td2.setBackgroundColor(Color.BLUE);
+ activity2.setTaskDescription(td2);
+
+ // Ensure the td is set for the original root task
+ assertEquals(Color.BLUE, task.getTaskDescription().getBackgroundColor());
+
+ task.positionChildAt(POSITION_TOP, activity1, false /* includeParents */);
+
+ // Ensure the td is set for the original root task
+ assertEquals(Color.RED, task.getTaskDescription().getBackgroundColor());
+ }
+
private Task getTestTask() {
return new TaskBuilder(mSupervisor).setCreateActivity(true).build();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
index f1d84cf..529e9b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
@@ -49,6 +49,7 @@
import org.junit.Test;
import org.junit.rules.TestName;
+import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -144,7 +145,7 @@
}
}
return false;
- }, TIMEOUT_S, TimeUnit.SECONDS);
+ }, Duration.ofSeconds(TIMEOUT_S));
assertAndDumpWindowState(TAG, "Failed to find window or was not marked trusted",
foundTrusted[0]);
@@ -209,7 +210,7 @@
}
}
return foundTrusted[0] && foundTrusted[1];
- }, TIMEOUT_S, TimeUnit.SECONDS);
+ }, Duration.ofSeconds(TIMEOUT_S));
if (!foundTrusted[0] || !foundTrusted[1]) {
CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName());
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 61698db..0468f48 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9992,6 +9992,51 @@
@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool";
+ /** @hide */
+ @IntDef({
+ CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC,
+ CARRIER_ROAMING_NTN_CONNECT_MANUAL,
+ })
+ public @interface CARRIER_ROAMING_NTN_CONNECT_TYPE {}
+
+ /**
+ * Device can connect to carrier roaming non-terrestrial network automatically.
+ * @hide
+ */
+ public static final int CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC = 0;
+ /**
+ * Device can connect to carrier roaming non-terrestrial network only if user manually triggers
+ * satellite connection.
+ * @hide
+ */
+ public static final int CARRIER_ROAMING_NTN_CONNECT_MANUAL = 1;
+ /**
+ * Indicates carrier roaming non-terrestrial network connect type that the device can use to
+ * perform satellite communication.
+ * If this key is set to CARRIER_ROAMING_NTN_CONNECT_MANUAL then connect button will be
+ * displayed to user when the device is eligible to use carrier roaming
+ * non-terrestrial network.
+ * @hide
+ */
+ public static final String KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT =
+ "carrier_roaming_ntn_connect_type_int";
+
+ /**
+ * The carrier roaming non-terrestrial network hysteresis time in seconds.
+ *
+ * If the device supports P2P satellite messaging which is defined by
+ * {@link CarrierConfigManager#KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE}
+ * and the device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi,
+ * then hysteresis timer defined by this key will start.
+ * After the timer is expired, device is marked as eligible for satellite communication.
+ *
+ * The default value is 180 seconds.
+ *
+ * @hide
+ */
+ public static final String KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT =
+ "carrier_supported_satellite_notification_hysteresis_sec_int";
+
/**
* 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.
@@ -11150,6 +11195,8 @@
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.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0);
+ sDefaults.putInt(KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT, 180);
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/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
index 36cbf1a..365a0ea 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -27,15 +27,6 @@
where things are arranged differently and to circle back up to the top once we reach the
bottom. -->
- <!-- View used for testing sourceRectHint. -->
- <View
- android:id="@+id/source_rect"
- android:layout_width="320dp"
- android:layout_height="180dp"
- android:visibility="gone"
- android:background="@android:color/holo_green_light"
- />
-
<Button
android:id="@+id/enter_pip"
android:layout_width="wrap_content"
@@ -122,12 +113,11 @@
android:onClick="onRatioSelected"/>
</RadioGroup>
- <Button
+ <CheckBox
android:id="@+id/set_source_rect_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="Set SourceRectHint"
- android:onClick="setSourceRectHint"/>
+ android:text="Set SourceRectHint"/>
<TextView
android:layout_width="wrap_content"
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
index 27eb5a0..13d7f7f 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
@@ -43,10 +43,10 @@
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Rational;
import android.view.View;
-import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CheckBox;
@@ -70,7 +70,7 @@
*/
private static final String TITLE_STATE_PAUSED = "TestApp media is paused";
- private static final Rational RATIO_DEFAULT = null;
+ private static final Rational RATIO_DEFAULT = new Rational(16, 9);
private static final Rational RATIO_SQUARE = new Rational(1, 1);
private static final Rational RATIO_WIDE = new Rational(2, 1);
private static final Rational RATIO_TALL = new Rational(1, 2);
@@ -88,8 +88,7 @@
"com.android.wm.shell.flicker.testapp.ASPECT_RATIO";
private final PictureInPictureParams.Builder mPipParamsBuilder =
- new PictureInPictureParams.Builder()
- .setAspectRatio(RATIO_DEFAULT);
+ new PictureInPictureParams.Builder();
private MediaSession mMediaSession;
private final PlaybackState.Builder mPlaybackStateBuilder = new PlaybackState.Builder()
.setActions(ACTION_PLAY | ACTION_PAUSE | ACTION_STOP)
@@ -139,6 +138,9 @@
}
};
+ private Rational mAspectRatio = RATIO_DEFAULT;
+ private boolean mEnableSourceRectHint;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -156,6 +158,14 @@
findViewById(R.id.media_session_stop)
.setOnClickListener(v -> updateMediaSessionState(STATE_STOPPED));
+ final CheckBox setSourceRectHintCheckBox = findViewById(R.id.set_source_rect_hint);
+ setSourceRectHintCheckBox.setOnCheckedChangeListener((v, isChecked) -> {
+ if (mEnableSourceRectHint != isChecked) {
+ mEnableSourceRectHint = isChecked;
+ updateSourceRectHint();
+ }
+ });
+
mMediaSession = new MediaSession(this, "WMShell_TestApp");
mMediaSession.setPlaybackState(mPlaybackStateBuilder.build());
mMediaSession.setCallback(new MediaSession.Callback() {
@@ -250,47 +260,64 @@
}
}
+ private void updateSourceRectHint() {
+ if (!mEnableSourceRectHint) return;
+ // Similar to PipUtils#getEnterPipWithOverlaySrcRectHint, crop the display bounds
+ // as source rect hint based on the current aspect ratio.
+ final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ final Rect displayBounds = new Rect(0, 0,
+ displayMetrics.widthPixels, displayMetrics.heightPixels);
+ final Rect sourceRectHint = getEnterPipWithOverlaySrcRectHint(
+ displayBounds, mAspectRatio.floatValue());
+ mPipParamsBuilder
+ .setAspectRatio(mAspectRatio)
+ .setSourceRectHint(sourceRectHint);
+ setPictureInPictureParams(mPipParamsBuilder.build());
+ }
+
/**
- * Adds a temporary view used for testing sourceRectHint.
- *
+ * Crop a Rect matches the aspect ratio and pivots at the center point.
+ * This is a counterpart of {@link PipUtils#getEnterPipWithOverlaySrcRectHint}
*/
- public void setSourceRectHint(View v) {
- View rectView = findViewById(R.id.source_rect);
- if (rectView != null) {
- rectView.setVisibility(View.VISIBLE);
- rectView.getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- Rect boundingRect = new Rect();
- rectView.getGlobalVisibleRect(boundingRect);
- mPipParamsBuilder.setSourceRectHint(boundingRect);
- setPictureInPictureParams(mPipParamsBuilder.build());
- rectView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
- }
- });
- rectView.invalidate(); // changing the visibility, invalidating to redraw the view
+ private Rect getEnterPipWithOverlaySrcRectHint(Rect appBounds, float aspectRatio) {
+ final float appBoundsAspectRatio = appBounds.width() / (float) appBounds.height();
+ final int width, height;
+ int left = appBounds.left;
+ int top = appBounds.top;
+ if (appBoundsAspectRatio < aspectRatio) {
+ width = appBounds.width();
+ height = (int) (width / aspectRatio);
+ top = appBounds.top + (appBounds.height() - height) / 2;
+ } else {
+ height = appBounds.height();
+ width = (int) (height * aspectRatio);
+ left = appBounds.left + (appBounds.width() - width) / 2;
}
+ return new Rect(left, top, left + width, top + height);
}
public void onRatioSelected(View v) {
switch (v.getId()) {
case R.id.ratio_default:
- mPipParamsBuilder.setAspectRatio(RATIO_DEFAULT);
+ mAspectRatio = RATIO_DEFAULT;
break;
case R.id.ratio_square:
- mPipParamsBuilder.setAspectRatio(RATIO_SQUARE);
+ mAspectRatio = RATIO_SQUARE;
break;
case R.id.ratio_wide:
- mPipParamsBuilder.setAspectRatio(RATIO_WIDE);
+ mAspectRatio = RATIO_WIDE;
break;
case R.id.ratio_tall:
- mPipParamsBuilder.setAspectRatio(RATIO_TALL);
+ mAspectRatio = RATIO_TALL;
break;
}
+ setPictureInPictureParams(mPipParamsBuilder.setAspectRatio(mAspectRatio).build());
+ if (mEnableSourceRectHint) {
+ updateSourceRectHint();
+ }
}
private void updateMediaSessionState(int newState) {
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 8d1fc50..d32cedb 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -40,7 +40,7 @@
import com.android.cts.input.DebugInputRule
import com.android.cts.input.UinputTouchScreen
-import java.util.concurrent.TimeUnit
+import java.time.Duration
import org.junit.After
import org.junit.Assert.assertEquals
@@ -193,6 +193,6 @@
val flags = " -W -n "
val startCmd = "am start $flags $PACKAGE_NAME/.UnresponsiveGestureMonitorActivity"
instrumentation.uiAutomation.executeShellCommand(startCmd)
- waitForStableWindowGeometry(5L, TimeUnit.SECONDS)
+ waitForStableWindowGeometry(Duration.ofSeconds(5))
}
}
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
index 4c531b8..a4085e5 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/Android.bp
@@ -23,6 +23,7 @@
resource_dirs: ["res"],
libs: ["android.test.runner"],
static_libs: [
+ "androidx.core_core",
"androidx.test.ext.junit",
"androidx.test.rules",
"compatibility-device-util-axt",
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
index 7806061..b4a7663 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
@@ -86,11 +86,12 @@
@Test
public void testAllFieldsValidV1() throws Exception {
System.out.println("starting testAllFieldsValidV1.");
- new AppInfoFactory()
- .createFromOdElement(
- TestUtils.getElementFromResource(
- Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME)),
- 1L);
+ var unused =
+ new AppInfoFactory()
+ .createFromOdElement(
+ TestUtils.getElementFromResource(
+ Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME)),
+ 1L);
}
/** Test for unrecognized field v1. */
@@ -133,7 +134,7 @@
TestUtils.getElementFromResource(
Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_V1_FILE_NAME));
TestUtils.removeOdChildEleWithName(appInfoEle, optField);
- new AppInfoFactory().createFromOdElement(appInfoEle, 1L);
+ var unused = new AppInfoFactory().createFromOdElement(appInfoEle, 1L);
}
}
@@ -202,7 +203,7 @@
Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
ele.removeAttribute(optField);
AppInfo appInfo = new AppInfoFactory().createFromHrElement(ele, DEFAULT_VERSION);
- appInfo.toOdDomElement(TestUtils.document());
+ var unused = appInfo.toOdDomElement(TestUtils.document());
}
for (String optField : OPTIONAL_FIELD_NAMES_OD) {
@@ -211,7 +212,7 @@
Paths.get(APP_INFO_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
TestUtils.removeOdChildEleWithName(ele, optField);
AppInfo appInfo = new AppInfoFactory().createFromOdElement(ele, DEFAULT_VERSION);
- appInfo.toHrDomElement(TestUtils.document());
+ var unused = appInfo.toHrDomElement(TestUtils.document());
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
index a4472b1..2746800 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
@@ -99,7 +99,7 @@
developerInfoEle.removeAttribute(optField);
DeveloperInfo developerInfo =
new DeveloperInfoFactory().createFromHrElement(developerInfoEle);
- developerInfo.toOdDomElement(TestUtils.document());
+ var unused = developerInfo.toOdDomElement(TestUtils.document());
}
for (String optField : OPTIONAL_FIELD_NAMES_OD) {
@@ -109,7 +109,7 @@
TestUtils.removeOdChildEleWithName(developerInfoEle, optField);
DeveloperInfo developerInfo =
new DeveloperInfoFactory().createFromOdElement(developerInfoEle);
- developerInfo.toHrDomElement(TestUtils.document());
+ var unused = developerInfo.toHrDomElement(TestUtils.document());
}
}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
index 9d197a2..27f8720 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SecurityLabelsTest.java
@@ -64,7 +64,7 @@
Paths.get(SECURITY_LABELS_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
ele.removeAttribute(optField);
SecurityLabels securityLabels = new SecurityLabelsFactory().createFromHrElement(ele);
- securityLabels.toOdDomElement(TestUtils.document());
+ var unused = securityLabels.toOdDomElement(TestUtils.document());
}
for (String optField : OPTIONAL_FIELD_NAMES_OD) {
var ele =
@@ -72,7 +72,7 @@
Paths.get(SECURITY_LABELS_OD_PATH, ALL_FIELDS_VALID_FILE_NAME));
TestUtils.removeOdChildEleWithName(ele, optField);
SecurityLabels securityLabels = new SecurityLabelsFactory().createFromOdElement(ele);
- securityLabels.toHrDomElement(TestUtils.document());
+ var unused = securityLabels.toHrDomElement(TestUtils.document());
}
}
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
index f7162f6..5a214b7 100644
--- a/wifi/wifi.aconfig
+++ b/wifi/wifi.aconfig
@@ -35,3 +35,14 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "hotspot_network_connecting_state_for_details_page"
+ namespace: "wifi"
+ description: "Update getConnectedState in HotspotNetworkEntry so that details page displays correctly."
+ bug: "321096462"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}