Merge "Skip syncing local window for rotation with shell transition" into tm-qpr-dev
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 e8bcfd2..c94cc8f 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -2895,7 +2895,11 @@
} else {
needsPermission = false;
lowerQuota = allowWhileIdle;
- idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
+ idleOptions = (allowWhileIdle || (alarmClock != null))
+ // This avoids exceptions on existing alarms when the app upgrades to
+ // target S. Note that FGS from pre-S apps isn't restricted anyway.
+ ? mOptsWithFgs.toBundle()
+ : null;
if (exact) {
exactAllowReason = EXACT_ALLOW_REASON_COMPAT;
}
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index f97415c..1769993e 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -20,6 +20,7 @@
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
+import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -66,7 +67,7 @@
private CharSequence mName;
private String mDescription;
private boolean mBlocked;
- private List<NotificationChannel> mChannels = new ArrayList<>();
+ private ParceledListSlice<NotificationChannel> mChannels;
// Bitwise representation of fields that have been changed by the user
private int mUserLockedFields;
@@ -100,7 +101,8 @@
} else {
mDescription = null;
}
- in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
+ mChannels = in.readParcelable(
+ NotificationChannelGroup.class.getClassLoader(), ParceledListSlice.class);
mBlocked = in.readBoolean();
mUserLockedFields = in.readInt();
}
@@ -127,7 +129,7 @@
} else {
dest.writeByte((byte) 0);
}
- dest.writeParcelableList(mChannels, flags);
+ dest.writeParcelable(mChannels, flags);
dest.writeBoolean(mBlocked);
dest.writeInt(mUserLockedFields);
}
@@ -157,7 +159,7 @@
* Returns the list of channels that belong to this group
*/
public List<NotificationChannel> getChannels() {
- return mChannels;
+ return mChannels == null ? new ArrayList<>() : mChannels.getList();
}
/**
@@ -191,15 +193,8 @@
/**
* @hide
*/
- public void addChannel(NotificationChannel channel) {
- mChannels.add(channel);
- }
-
- /**
- * @hide
- */
public void setChannels(List<NotificationChannel> channels) {
- mChannels = channels;
+ mChannels = new ParceledListSlice<>(channels);
}
/**
@@ -334,7 +329,7 @@
proto.write(NotificationChannelGroupProto.NAME, mName.toString());
proto.write(NotificationChannelGroupProto.DESCRIPTION, mDescription);
proto.write(NotificationChannelGroupProto.IS_BLOCKED, mBlocked);
- for (NotificationChannel channel : mChannels) {
+ for (NotificationChannel channel : mChannels.getList()) {
channel.dumpDebug(proto, NotificationChannelGroupProto.CHANNELS);
}
proto.end(token);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 44dc28d..c15b3e0 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -713,6 +713,15 @@
if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) {
return null;
}
+
+ final ApplicationInfo applicationInfo;
+ if ((flags & (PackageManager.GET_ACTIVITIES | PackageManager.GET_RECEIVERS
+ | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS)) != 0) {
+ applicationInfo = generateApplicationInfo(p, flags, state, userId);
+ } else {
+ applicationInfo = null;
+ }
+
PackageInfo pi = new PackageInfo();
pi.packageName = p.packageName;
pi.splitNames = p.splitNames;
@@ -773,7 +782,7 @@
if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) {
continue;
}
- res[num++] = generateActivityInfo(a, flags, state, userId);
+ res[num++] = generateActivityInfo(a, flags, state, userId, applicationInfo);
}
}
pi.activities = ArrayUtils.trimToSize(res, num);
@@ -787,7 +796,7 @@
for (int i = 0; i < N; i++) {
final Activity a = p.receivers.get(i);
if (isMatch(state, a.info, flags)) {
- res[num++] = generateActivityInfo(a, flags, state, userId);
+ res[num++] = generateActivityInfo(a, flags, state, userId, applicationInfo);
}
}
pi.receivers = ArrayUtils.trimToSize(res, num);
@@ -801,7 +810,7 @@
for (int i = 0; i < N; i++) {
final Service s = p.services.get(i);
if (isMatch(state, s.info, flags)) {
- res[num++] = generateServiceInfo(s, flags, state, userId);
+ res[num++] = generateServiceInfo(s, flags, state, userId, applicationInfo);
}
}
pi.services = ArrayUtils.trimToSize(res, num);
@@ -815,7 +824,8 @@
for (int i = 0; i < N; i++) {
final Provider pr = p.providers.get(i);
if (isMatch(state, pr.info, flags)) {
- res[num++] = generateProviderInfo(pr, flags, state, userId);
+ res[num++] = generateProviderInfo(pr, flags, state, userId,
+ applicationInfo);
}
}
pi.providers = ArrayUtils.trimToSize(res, num);
@@ -8216,6 +8226,11 @@
@UnsupportedAppUsage
public static final ActivityInfo generateActivityInfo(Activity a, int flags,
FrameworkPackageUserState state, int userId) {
+ return generateActivityInfo(a, flags, state, userId, null);
+ }
+
+ private static ActivityInfo generateActivityInfo(Activity a, int flags,
+ FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) {
if (a == null) return null;
if (!checkUseInstalledOrHidden(flags, state, a.owner.applicationInfo)) {
return null;
@@ -8227,7 +8242,12 @@
// Make shallow copies so we can store the metadata safely
ActivityInfo ai = new ActivityInfo(a.info);
ai.metaData = a.metaData;
- ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
+
+ if (applicationInfo == null) {
+ applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
+ }
+ ai.applicationInfo = applicationInfo;
+
return ai;
}
@@ -8308,6 +8328,11 @@
@UnsupportedAppUsage
public static final ServiceInfo generateServiceInfo(Service s, int flags,
FrameworkPackageUserState state, int userId) {
+ return generateServiceInfo(s, flags, state, userId, null);
+ }
+
+ private static ServiceInfo generateServiceInfo(Service s, int flags,
+ FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) {
if (s == null) return null;
if (!checkUseInstalledOrHidden(flags, state, s.owner.applicationInfo)) {
return null;
@@ -8319,7 +8344,12 @@
// Make shallow copies so we can store the metadata safely
ServiceInfo si = new ServiceInfo(s.info);
si.metaData = s.metaData;
- si.applicationInfo = generateApplicationInfo(s.owner, flags, state, userId);
+
+ if (applicationInfo == null) {
+ applicationInfo = generateApplicationInfo(s.owner, flags, state, userId);
+ }
+ si.applicationInfo = applicationInfo;
+
return si;
}
@@ -8406,13 +8436,18 @@
@UnsupportedAppUsage
public static final ProviderInfo generateProviderInfo(Provider p, int flags,
FrameworkPackageUserState state, int userId) {
+ return generateProviderInfo(p, flags, state, userId, null);
+ }
+
+ private static ProviderInfo generateProviderInfo(Provider p, int flags,
+ FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) {
if (p == null) return null;
if (!checkUseInstalledOrHidden(flags, state, p.owner.applicationInfo)) {
return null;
}
if (!copyNeeded(flags, p.owner, state, p.metaData, userId)
&& ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
- || p.info.uriPermissionPatterns == null)) {
+ || p.info.uriPermissionPatterns == null)) {
updateApplicationInfo(p.info.applicationInfo, flags, state);
return p.info;
}
@@ -8422,7 +8457,12 @@
if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
pi.uriPermissionPatterns = null;
}
- pi.applicationInfo = generateApplicationInfo(p.owner, flags, state, userId);
+
+ if (applicationInfo == null) {
+ applicationInfo = generateApplicationInfo(p.owner, flags, state, userId);
+ }
+ pi.applicationInfo = applicationInfo;
+
return pi;
}
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
index cbd8066..598170d 100644
--- a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryableArray.java
@@ -26,6 +26,10 @@
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
import java.util.ArrayList;
/**
@@ -51,28 +55,36 @@
return new PrimitiveArrayFiller() {
@Override
public void fillArray(Object arr, int size, ByteBuffer buffer) {
- buffer.asIntBuffer().get(int[].class.cast(arr), 0, size);
+ IntBuffer ib = buffer.asIntBuffer().get(int[].class.cast(arr), 0, size);
+ // Update buffer position since the IntBuffer has independent position.
+ buffer.position(buffer.position() + ib.position() * Integer.BYTES);
}
};
} else if (componentType == float.class) {
return new PrimitiveArrayFiller() {
@Override
public void fillArray(Object arr, int size, ByteBuffer buffer) {
- buffer.asFloatBuffer().get(float[].class.cast(arr), 0, size);
+ FloatBuffer fb =
+ buffer.asFloatBuffer().get(float[].class.cast(arr), 0, size);
+ buffer.position(buffer.position() + fb.position() * Float.BYTES);
}
};
} else if (componentType == long.class) {
return new PrimitiveArrayFiller() {
@Override
public void fillArray(Object arr, int size, ByteBuffer buffer) {
- buffer.asLongBuffer().get(long[].class.cast(arr), 0, size);
+ LongBuffer lb =
+ buffer.asLongBuffer().get(long[].class.cast(arr), 0, size);
+ buffer.position(buffer.position() + lb.position() * Long.BYTES);
}
};
} else if (componentType == double.class) {
return new PrimitiveArrayFiller() {
@Override
public void fillArray(Object arr, int size, ByteBuffer buffer) {
- buffer.asDoubleBuffer().get(double[].class.cast(arr), 0, size);
+ DoubleBuffer db =
+ buffer.asDoubleBuffer().get(double[].class.cast(arr), 0, size);
+ buffer.position(buffer.position() + db.position() * Double.BYTES);
}
};
} else if (componentType == byte.class) {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 6049199..195cf82 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -86,7 +86,12 @@
/** Flag to enable/disable guest mode UX changes as mentioned in b/214031645
* @hide
*/
- public static final String SETTINGS_GUEST_MODE_UX_CHANGES = "settings_guest_mode_ux_changes";
+ public static final String SETTINGS_GUEST_MODE_UX_CHANGES = "settings_guest_mode_ux_changes";
+
+ /** Support Clear Calling feature.
+ * @hide
+ */
+ public static final String SETTINGS_ENABLE_CLEAR_CALLING = "settings_enable_clear_calling";
private static final Map<String, String> DEFAULT_FLAGS;
@@ -116,6 +121,7 @@
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
DEFAULT_FLAGS.put(SETTINGS_GUEST_MODE_UX_CHANGES, "true");
+ DEFAULT_FLAGS.put(SETTINGS_ENABLE_CLEAR_CALLING, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 6a0ec33..c198098 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -90,8 +90,10 @@
ITYPE_IME,
ITYPE_CLIMATE_BAR,
ITYPE_EXTRA_NAVIGATION_BAR,
- ITYPE_LOCAL_NAVIGATION_BAR_1,
- ITYPE_LOCAL_NAVIGATION_BAR_2
+ ITYPE_LEFT_GENERIC_OVERLAY,
+ ITYPE_TOP_GENERIC_OVERLAY,
+ ITYPE_RIGHT_GENERIC_OVERLAY,
+ ITYPE_BOTTOM_GENERIC_OVERLAY
})
public @interface InternalInsetsType {}
@@ -135,10 +137,12 @@
public static final int ITYPE_EXTRA_NAVIGATION_BAR = 21;
/** Additional types for local insets. **/
- public static final int ITYPE_LOCAL_NAVIGATION_BAR_1 = 22;
- public static final int ITYPE_LOCAL_NAVIGATION_BAR_2 = 23;
+ public static final int ITYPE_LEFT_GENERIC_OVERLAY = 22;
+ public static final int ITYPE_TOP_GENERIC_OVERLAY = 23;
+ public static final int ITYPE_RIGHT_GENERIC_OVERLAY = 24;
+ public static final int ITYPE_BOTTOM_GENERIC_OVERLAY = 25;
- static final int LAST_TYPE = ITYPE_LOCAL_NAVIGATION_BAR_2;
+ static final int LAST_TYPE = ITYPE_BOTTOM_GENERIC_OVERLAY;
public static final int SIZE = LAST_TYPE + 1;
// Derived types
@@ -698,8 +702,12 @@
if ((types & Type.NAVIGATION_BARS) != 0) {
result.add(ITYPE_NAVIGATION_BAR);
result.add(ITYPE_EXTRA_NAVIGATION_BAR);
- result.add(ITYPE_LOCAL_NAVIGATION_BAR_1);
- result.add(ITYPE_LOCAL_NAVIGATION_BAR_2);
+ }
+ if ((types & Type.GENERIC_OVERLAYS) != 0) {
+ result.add(ITYPE_LEFT_GENERIC_OVERLAY);
+ result.add(ITYPE_TOP_GENERIC_OVERLAY);
+ result.add(ITYPE_RIGHT_GENERIC_OVERLAY);
+ result.add(ITYPE_BOTTOM_GENERIC_OVERLAY);
}
if ((types & Type.CAPTION_BAR) != 0) {
result.add(ITYPE_CAPTION_BAR);
@@ -740,9 +748,12 @@
return Type.STATUS_BARS;
case ITYPE_NAVIGATION_BAR:
case ITYPE_EXTRA_NAVIGATION_BAR:
- case ITYPE_LOCAL_NAVIGATION_BAR_1:
- case ITYPE_LOCAL_NAVIGATION_BAR_2:
return Type.NAVIGATION_BARS;
+ case ITYPE_LEFT_GENERIC_OVERLAY:
+ case ITYPE_TOP_GENERIC_OVERLAY:
+ case ITYPE_RIGHT_GENERIC_OVERLAY:
+ case ITYPE_BOTTOM_GENERIC_OVERLAY:
+ return Type.GENERIC_OVERLAYS;
case ITYPE_CAPTION_BAR:
return Type.CAPTION_BAR;
case ITYPE_IME:
@@ -861,10 +872,14 @@
return "ITYPE_CLIMATE_BAR";
case ITYPE_EXTRA_NAVIGATION_BAR:
return "ITYPE_EXTRA_NAVIGATION_BAR";
- case ITYPE_LOCAL_NAVIGATION_BAR_1:
- return "ITYPE_LOCAL_NAVIGATION_BAR_1";
- case ITYPE_LOCAL_NAVIGATION_BAR_2:
- return "ITYPE_LOCAL_NAVIGATION_BAR_2";
+ case ITYPE_LEFT_GENERIC_OVERLAY:
+ return "ITYPE_LEFT_GENERIC_OVERLAY";
+ case ITYPE_TOP_GENERIC_OVERLAY:
+ return "ITYPE_TOP_GENERIC_OVERLAY";
+ case ITYPE_RIGHT_GENERIC_OVERLAY:
+ return "ITYPE_RIGHT_GENERIC_OVERLAY";
+ case ITYPE_BOTTOM_GENERIC_OVERLAY:
+ return "ITYPE_BOTTOM_GENERIC_OVERLAY";
default:
return "ITYPE_UNKNOWN_" + type;
}
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index c846175..c1dddbe 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1423,9 +1423,11 @@
static final int DISPLAY_CUTOUT = 1 << 7;
- static final int LAST = 1 << 8;
- static final int SIZE = 9;
- static final int WINDOW_DECOR = LAST;
+ static final int WINDOW_DECOR = 1 << 8;
+
+ static final int GENERIC_OVERLAYS = 1 << 9;
+ static final int LAST = GENERIC_OVERLAYS;
+ static final int SIZE = 10;
static int indexOf(@InsetsType int type) {
switch (type) {
@@ -1447,6 +1449,8 @@
return 7;
case WINDOW_DECOR:
return 8;
+ case GENERIC_OVERLAYS:
+ return 9;
default:
throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
+ " type=" + type);
@@ -1482,6 +1486,9 @@
if ((types & WINDOW_DECOR) != 0) {
result.append("windowDecor |");
}
+ if ((types & GENERIC_OVERLAYS) != 0) {
+ result.append("genericOverlays |");
+ }
if (result.length() > 0) {
result.delete(result.length() - 2, result.length());
}
@@ -1494,7 +1501,8 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {STATUS_BARS, NAVIGATION_BARS, CAPTION_BAR, IME, WINDOW_DECOR,
- SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT})
+ SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT,
+ GENERIC_OVERLAYS})
public @interface InsetsType {
}
@@ -1586,7 +1594,7 @@
* {@link #navigationBars()}, but not {@link #ime()}.
*/
public static @InsetsType int systemBars() {
- return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR;
+ return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR | GENERIC_OVERLAYS;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index dd990e7..63d42c0 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2367,6 +2367,14 @@
public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
/**
+ * Flag to allow this window to have unrestricted gesture exclusion.
+ *
+ * @see View#setSystemGestureExclusionRects(List)
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 0x00000020;
+
+ /**
* Never animate position changes of the window.
*
* {@hide}
@@ -2586,6 +2594,7 @@
PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
+ PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
PRIVATE_FLAG_NO_MOVE_ANIMATION,
PRIVATE_FLAG_COMPATIBLE_WINDOW,
PRIVATE_FLAG_SYSTEM_ERROR,
@@ -2633,6 +2642,10 @@
equals = SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
name = "SHOW_FOR_ALL_USERS"),
@ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
+ equals = PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
+ name = "UNRESTRICTED_GESTURE_EXCLUSION"),
+ @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_NO_MOVE_ANIMATION,
equals = PRIVATE_FLAG_NO_MOVE_ANIMATION,
name = "NO_MOVE_ANIMATION"),
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7439b2f..5e99d6c4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6498,6 +6498,13 @@
<permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an app to set gesture exclusion without restrictions on the vertical extent of the
+ exclusions (see {@link android.view.View#setSystemGestureExclusionRects}).
+ @hide
+ -->
+ <permission android:name="android.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION"
+ android:protectionLevel="signature|privileged|recents" />
+
<!-- @SystemApi Allows TV input apps and TV apps to use TIS extension interfaces for
domain-specific features.
<p>Protection level: signature|privileged|vendorPrivileged
diff --git a/core/res/res/layout-car/car_alert_dialog.xml b/core/res/res/layout-car/car_alert_dialog.xml
index 2e7b62c..3c7a5c5 100644
--- a/core/res/res/layout-car/car_alert_dialog.xml
+++ b/core/res/res/layout-car/car_alert_dialog.xml
@@ -54,7 +54,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_view_start_margin"
android:layout_marginEnd="@dimen/text_view_end_margin"
- style="@style/CarBody4"/>
+ style="@style/CarDialogMessageText"/>
<!-- we don't need this spacer, but the id needs to be here for compatibility -->
<Space
diff --git a/core/res/res/layout-car/car_alert_dialog_button_bar.xml b/core/res/res/layout-car/car_alert_dialog_button_bar.xml
index 4f815b8..43fd1eb 100644
--- a/core/res/res/layout-car/car_alert_dialog_button_bar.xml
+++ b/core/res/res/layout-car/car_alert_dialog_button_bar.xml
@@ -16,13 +16,13 @@
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/buttonPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:scrollbarAlwaysDrawVerticalTrack="true"
- android:scrollIndicators="top|bottom"
- android:fillViewport="true"
- style="?attr/buttonBarStyle">
+ android:id="@+id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:scrollIndicators="top|bottom"
+ android:fillViewport="true"
+ style="?attr/buttonBarStyle">
<com.android.internal.widget.ButtonBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -35,7 +35,7 @@
<Button
android:id="@+id/button3"
- style="@style/CarAction1"
+ style="@style/CarDialogButtonText"
android:minWidth="@dimen/car_touch_target_size"
android:paddingStart="@dimen/car_padding_2"
android:paddingEnd="@dimen/car_padding_2"
@@ -46,7 +46,7 @@
<Button
android:id="@+id/button2"
- style="@style/CarAction1"
+ style="@style/CarDialogButtonText"
android:minWidth="@dimen/car_touch_target_size"
android:paddingStart="@dimen/car_padding_2"
android:paddingEnd="@dimen/car_padding_2"
@@ -57,7 +57,7 @@
<Button
android:id="@+id/button1"
- style="@style/CarAction1"
+ style="@style/CarDialogButtonText"
android:minWidth="@dimen/car_touch_target_size"
android:paddingStart="@dimen/car_padding_2"
android:paddingEnd="@dimen/car_padding_2"
diff --git a/core/res/res/values/styles_car.xml b/core/res/res/values/styles_car.xml
index ca3ba93..0655fde 100644
--- a/core/res/res/values/styles_car.xml
+++ b/core/res/res/values/styles_car.xml
@@ -14,96 +14,15 @@
limitations under the License.
-->
<resources>
- <!-- Car text -->
- <style name="CarBody1">
- <item name="textStyle">normal</item>
- <item name="textSize">@dimen/car_body1_size</item>
- <item name="textColor">@color/car_body1</item>
- </style>
-
- <style name="CarBody1.Light">
- <item name="textColor">@color/car_body1_light</item>
- </style>
-
- <style name="CarBody1.Dark">
- <item name="textColor">@color/car_body2_dark</item>
- </style>
-
- <style name="CarBody2">
- <item name="textStyle">normal</item>
- <item name="textSize">@dimen/car_body2_size</item>
- <item name="textColor">@color/car_body2</item>
- </style>
-
- <style name="CarBody2.Dark">
- <item name="textColor">@color/car_body2_dark</item>
- </style>
- <style name="CarBody2.Light">
- <item name="textColor">@color/car_body2_light</item>
- </style>
-
- <style name="CarBody3">
- <item name="textStyle">normal</item>
- <item name="textSize">@dimen/car_body3_size</item>
- <item name="textColor">@color/car_body3</item>
- </style>
-
- <!-- The smallest styling for body text. The color of this text changes based on the day/night
- mode. -->
- <style name="CarBody4">
+ <!-- The Dialog message text style-->
+ <style name="CarDialogMessageText">
<item name="textStyle">normal</item>
<item name="textSize">@dimen/car_body4_size</item>
<item name="textColor">@color/car_body4</item>
</style>
-
- <style name="CarAction1">
- <item name="textStyle">bold</item>
- <item name="textSize">@dimen/car_action1_size</item>
- <item name="textColor">@color/control_default_material</item>
- </style>
-
- <style name="CarAction1.Dark">
- <item name="textColor">@color/car_highlight_dark</item>
- </style>
- <style name="CarAction1.Light">
- <item name="textColor">@color/car_highlight_light</item>
- </style>
-
- <!-- The styling for title text. The color of this text changes based on day/night mode. -->
- <style name="CarTitle" >
- <item name="textStyle">bold</item>
- <item name="textSize">@dimen/car_title2_size</item>
- <item name="textColor">@color/car_title</item>
- </style>
-
- <!-- Title text that is permanently a dark color. -->
- <style name="CarTitle.Dark" >
- <item name="textColor">@color/car_title_dark</item>
- </style>
-
- <!-- Title text that is permanently a light color. -->
- <style name="CarTitle.Light" >
- <item name="textColor">@color/car_title_light</item>
- </style>
-
- <!-- Action bar -->
- <style name="ActionBarTitle" parent="@style/Widget.DeviceDefault.TextView">
- <item name="android:singleLine">true</item>
- <item name="android:textAppearance">?attr/textAppearanceLarge</item>
- </style>
-
- <style name="ActionBarButton"
- parent="@style/Widget.DeviceDefault.Button.Borderless.Colored">
- <item name="android:textAppearance">@style/ActionBarButtonTextAppearance</item>
- <!-- Button's internal horizontal padding -->
- <item name="android:paddingStart">@*android:dimen/car_padding_3</item>
- <item name="android:paddingEnd">@*android:dimen/car_padding_3</item>
- <item name="android:drawablePadding">@*android:dimen/car_padding_2</item>
- <item name="android:maxWidth">@*android:dimen/action_bar_button_max_width</item>
- </style>
-
- <style name="ActionBarButtonTextAppearance"
- parent="@style/TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored">
- <item name="android:textAllCaps">false</item>
+ <!-- This style makes Dialog button text use medium font weight. -->
+ <style name="CarDialogButtonText">
+ <item name="android:textAppearance">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+ <item name="android:textColor">@color/control_default_material</item>
</style>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
deleted file mode 100644
index 73fd693..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-import java.io.PrintWriter;
-
-/**
- * An entry point into the shell for dumping shell internal state and running adb commands.
- *
- * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
- */
-@ExternalThread
-public interface ShellCommandHandler {
- /**
- * Dumps the shell state.
- */
- void dump(PrintWriter pw);
-
- /**
- * Handles a shell command.
- */
- boolean handleCommand(final String[] args, PrintWriter pw);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
deleted file mode 100644
index d7010b1..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * An entry point into the shell for initializing shell internal state.
- */
-@ExternalThread
-public interface ShellInit {
- /**
- * Initializes the shell state.
- */
- void init();
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index b69cbfa..40cf9a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -727,31 +727,23 @@
ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) {
if (offsetX == 0 && offsetY == 0) {
wct.setBounds(taskInfo1.token, mBounds1);
- wct.setAppBounds(taskInfo1.token, null);
wct.setScreenSizeDp(taskInfo1.token,
SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
wct.setBounds(taskInfo2.token, mBounds2);
- wct.setAppBounds(taskInfo2.token, null);
wct.setScreenSizeDp(taskInfo2.token,
SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
} else {
- mTempRect.set(taskInfo1.configuration.windowConfiguration.getBounds());
+ getBounds1(mTempRect);
mTempRect.offset(offsetX, offsetY);
wct.setBounds(taskInfo1.token, mTempRect);
- mTempRect.set(taskInfo1.configuration.windowConfiguration.getAppBounds());
- mTempRect.offset(offsetX, offsetY);
- wct.setAppBounds(taskInfo1.token, mTempRect);
wct.setScreenSizeDp(taskInfo1.token,
taskInfo1.configuration.screenWidthDp,
taskInfo1.configuration.screenHeightDp);
- mTempRect.set(taskInfo2.configuration.windowConfiguration.getBounds());
+ getBounds2(mTempRect);
mTempRect.offset(offsetX, offsetY);
wct.setBounds(taskInfo2.token, mTempRect);
- mTempRect.set(taskInfo2.configuration.windowConfiguration.getAppBounds());
- mTempRect.offset(offsetX, offsetY);
- wct.setAppBounds(taskInfo2.token, mTempRect);
wct.setScreenSizeDp(taskInfo2.token,
taskInfo2.configuration.screenWidthDp,
taskInfo2.configuration.screenHeightDp);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 586eab0..f85f9d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -29,10 +29,8 @@
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellCommandHandler;
-import com.android.wm.shell.ShellCommandHandlerImpl;
-import com.android.wm.shell.ShellInit;
-import com.android.wm.shell.ShellInitImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.TaskViewFactoryController;
@@ -624,13 +622,9 @@
@WMSingleton
@Provides
- static ShellInit provideShellInit(ShellInitImpl impl) {
- return impl.asShellInit();
- }
-
- @WMSingleton
- @Provides
- static ShellInitImpl provideShellInitImpl(DisplayController displayController,
+ static ShellInit provideShellInitImpl(
+ ShellController shellController,
+ DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
DragAndDropController dragAndDropController,
@@ -648,7 +642,8 @@
Transitions transitions,
StartingWindowController startingWindow,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellInitImpl(displayController,
+ return new ShellInit(shellController,
+ displayController,
displayImeController,
displayInsetsController,
dragAndDropController,
@@ -668,19 +663,10 @@
mainExecutor);
}
- /**
- * Note, this is only optional because we currently pass this to the SysUI component scope and
- * for non-primary users, we may inject a null-optional for that dependency.
- */
@WMSingleton
@Provides
- static Optional<ShellCommandHandler> provideShellCommandHandler(ShellCommandHandlerImpl impl) {
- return Optional.of(impl.asShellCommandHandler());
- }
-
- @WMSingleton
- @Provides
- static ShellCommandHandlerImpl provideShellCommandHandlerImpl(
+ static ShellCommandHandler provideShellCommandHandlerImpl(
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<SplitScreenController> splitScreenOptional,
@@ -689,9 +675,9 @@
Optional<HideDisplayCutoutController> hideDisplayCutout,
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellCommandHandlerImpl(shellTaskOrganizer, kidsModeTaskOrganizer,
- splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
- recentTasksOptional, mainExecutor);
+ return new ShellCommandHandler(shellController, shellTaskOrganizer,
+ kidsModeTaskOrganizer, splitScreenOptional, pipOptional, oneHandedOptional,
+ hideDisplayCutout, recentTasksOptional, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index c672476..24f02ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -681,9 +681,12 @@
return;
}
+ if (mState.getState() == STATE_ACTIVE) {
+ mOneHandedUiEventLogger.writeEvent(
+ OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT);
+ }
+
mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct);
- mOneHandedUiEventLogger.writeEvent(
- OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
similarity index 82%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
index c5f7c19..0427efb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.sysui;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
@@ -34,8 +35,8 @@
*
* Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
*/
-public final class ShellCommandHandlerImpl {
- private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
+public final class ShellCommandHandler {
+ private static final String TAG = ShellCommandHandler.class.getSimpleName();
private final Optional<SplitScreenController> mSplitScreenOptional;
private final Optional<Pip> mPipOptional;
@@ -45,9 +46,9 @@
private final ShellTaskOrganizer mShellTaskOrganizer;
private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
private final ShellExecutor mMainExecutor;
- private final HandlerImpl mImpl = new HandlerImpl();
- public ShellCommandHandlerImpl(
+ public ShellCommandHandler(
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<SplitScreenController> splitScreenOptional,
@@ -64,14 +65,12 @@
mOneHandedOptional = oneHandedOptional;
mHideDisplayCutout = hideDisplayCutout;
mMainExecutor = mainExecutor;
- }
-
- public ShellCommandHandler asShellCommandHandler() {
- return mImpl;
+ // TODO(238217847): To be removed once the command handler dependencies are inverted
+ shellController.setShellCommandHandler(this);
}
/** Dumps WM Shell internal state. */
- private void dump(PrintWriter pw) {
+ public void dump(PrintWriter pw) {
mShellTaskOrganizer.dump(pw, "");
pw.println();
pw.println();
@@ -91,7 +90,7 @@
/** Returns {@code true} if command was found and executed. */
- private boolean handleCommand(final String[] args, PrintWriter pw) {
+ public boolean handleCommand(final String[] args, PrintWriter pw) {
if (args.length < 2) {
// Argument at position 0 is "WMShell".
return false;
@@ -164,28 +163,4 @@
pw.println(" Sets the position of the side-stage.");
return true;
}
-
- private class HandlerImpl implements ShellCommandHandler {
- @Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> ShellCommandHandlerImpl.this.dump(pw));
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to dump the Shell in 2s", e);
- }
- }
-
- @Override
- public boolean handleCommand(String[] args, PrintWriter pw) {
- try {
- boolean[] result = new boolean[1];
- mMainExecutor.executeBlocking(() -> {
- result[0] = ShellCommandHandlerImpl.this.handleCommand(args, pw);
- });
- return result[0];
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to handle Shell command in 2s", e);
- }
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
index 837acec..618028c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -47,6 +47,9 @@
private final ShellExecutor mMainExecutor;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
+ private ShellInit mShellInit;
+ private ShellCommandHandler mShellCommandHandler;
+
private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
@@ -66,6 +69,24 @@
}
/**
+ * Sets the init handler to call back to.
+ * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
+ * init handler to other classes.
+ */
+ public void setShellInit(ShellInit shellInit) {
+ mShellInit = shellInit;
+ }
+
+ /**
+ * Sets the command handler to call back to.
+ * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
+ * command handler to other classes.
+ */
+ public void setShellCommandHandler(ShellCommandHandler shellCommandHandler) {
+ mShellCommandHandler = shellCommandHandler;
+ }
+
+ /**
* Adds a new configuration listener. The configuration change callbacks are not made in any
* particular order.
*/
@@ -164,6 +185,38 @@
*/
@ExternalThread
private class ShellInterfaceImpl implements ShellInterface {
+
+ @Override
+ public void onInit() {
+ try {
+ mMainExecutor.executeBlocking(() -> mShellInit.init());
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to initialize the Shell in 2s", e);
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ try {
+ mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to dump the Shell in 2s", e);
+ }
+ }
+
+ @Override
+ public boolean handleCommand(String[] args, PrintWriter pw) {
+ try {
+ boolean[] result = new boolean[1];
+ mMainExecutor.executeBlocking(() -> {
+ result[0] = mShellCommandHandler.handleCommand(args, pw);
+ });
+ return result[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to handle Shell command in 2s", e);
+ }
+ }
+
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
mMainExecutor.execute(() ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
similarity index 92%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index 6694e44..2619b37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.sysui;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
@@ -26,13 +26,13 @@
import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
@@ -53,8 +53,8 @@
* The entry point implementation into the shell for initializing shell internal state. Classes
* which need to setup on start should inject an instance of this class and add an init callback.
*/
-public class ShellInitImpl {
- private static final String TAG = ShellInitImpl.class.getSimpleName();
+public class ShellInit {
+ private static final String TAG = ShellInit.class.getSimpleName();
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
@@ -75,12 +75,12 @@
private final Optional<RecentTasksController> mRecentTasks;
private final Optional<ActivityEmbeddingController> mActivityEmbeddingOptional;
- private final InitImpl mImpl = new InitImpl();
// An ordered list of init callbacks to be made once shell is first started
private final ArrayList<Pair<String, Runnable>> mInitCallbacks = new ArrayList<>();
private boolean mHasInitialized;
- public ShellInitImpl(
+ public ShellInit(
+ ShellController shellController,
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
@@ -117,10 +117,8 @@
mTransitions = transitions;
mMainExecutor = mainExecutor;
mStartingWindow = startingWindow;
- }
-
- public ShellInit asShellInit() {
- return mImpl;
+ // TODO(238217847): To be removed once the init dependencies are inverted
+ shellController.setShellInit(this);
}
private void legacyInit() {
@@ -210,16 +208,4 @@
mHasInitialized = true;
}
-
- @ExternalThread
- private class InitImpl implements ShellInit {
- @Override
- public void init() {
- try {
- mMainExecutor.executeBlocking(ShellInitImpl.this::init);
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to initialize the Shell in 2s", e);
- }
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
index a15ce5d..254c253 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -18,15 +18,32 @@
import android.content.res.Configuration;
+import java.io.PrintWriter;
+
/**
* General interface for notifying the Shell of common SysUI events like configuration or keyguard
* changes.
- *
- * TODO: Move ShellInit and ShellCommandHandler into this interface
*/
public interface ShellInterface {
/**
+ * Initializes the shell state.
+ */
+ default void onInit() {}
+
+ /**
+ * Dumps the shell state.
+ */
+ default void dump(PrintWriter pw) {}
+
+ /**
+ * Handles a shell command.
+ */
+ default boolean handleCommand(final String[] args, PrintWriter pw) {
+ return false;
+ }
+
+ /**
* Notifies the Shell that the configuration has changed.
*/
default void onConfigurationChanged(Configuration newConfiguration) {}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
similarity index 93%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
index ace8d36..219e5ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitImplTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
@@ -38,6 +38,8 @@
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
@@ -54,8 +56,9 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class ShellInitImplTest extends ShellTestCase {
+public class ShellInitTest extends ShellTestCase {
+ @Mock private ShellController mShellController;
@Mock private DisplayController mDisplayController;
@Mock private DisplayImeController mDisplayImeController;
@Mock private DisplayInsetsController mDisplayInsetsController;
@@ -75,12 +78,12 @@
@Mock private StartingWindowController mStartingWindow;
@Mock private ShellExecutor mMainExecutor;
- private ShellInitImpl mImpl;
+ private ShellInit mImpl;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mImpl = new ShellInitImpl(mDisplayController, mDisplayImeController,
+ mImpl = new ShellInit(mShellController, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mShellTaskOrganizer,
mKidsModeTaskOrganizer, mBubblesOptional, mSplitScreenOptional,
mPipTouchHandlerOptional, mFullscreenTaskListener, mUnfoldAnimationController,
diff --git a/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
index 50f69d1..c629d96 100644
--- a/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
+++ b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
@@ -71,6 +71,8 @@
<TextView
android:id="@+id/entity_header_second_summary"
style="@style/TextAppearance.EntityHeaderSummary"
+ android:singleLine="false"
+ android:maxLines="4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index dbdbdf6..2f36ab9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -515,11 +515,20 @@
dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE)
// Make sure the dialog is visible instantly and does not do any window animation.
- window.attributes.windowAnimations = R.style.Animation_LaunchAnimation
+ val attributes = window.attributes
+ attributes.windowAnimations = R.style.Animation_LaunchAnimation
// Ensure that the animation is not clipped by the display cut-out when animating this
// dialog into an app.
- window.attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+
+ // Ensure that the animation is not clipped by the navigation/task bars when animating this
+ // dialog into an app.
+ val wasFittingNavigationBars =
+ attributes.fitInsetsTypes and WindowInsets.Type.navigationBars() != 0
+ attributes.fitInsetsTypes =
+ attributes.fitInsetsTypes and WindowInsets.Type.navigationBars().inv()
+
window.attributes = window.attributes
// We apply the insets ourselves to make sure that the paddings are set on the correct
@@ -527,7 +536,13 @@
window.setDecorFitsSystemWindows(false)
val viewWithInsets = (dialogContentWithBackground.parent as ViewGroup)
viewWithInsets.setOnApplyWindowInsetsListener { view, windowInsets ->
- val insets = windowInsets.getInsets(WindowInsets.Type.displayCutout())
+ val type = if (wasFittingNavigationBars) {
+ WindowInsets.Type.displayCutout() or WindowInsets.Type.navigationBars()
+ } else {
+ WindowInsets.Type.displayCutout()
+ }
+
+ val insets = windowInsets.getInsets(type)
view.setPadding(insets.left, insets.top, insets.right, insets.bottom)
WindowInsets.CONSUMED
}
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index ec82ccf..5dc34b9 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<androidx.constraintlayout.motion.widget.MotionLayout
+<com.android.systemui.util.NoRemeasureMotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/split_shade_status_bar"
@@ -32,10 +32,37 @@
<androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:id="@+id/center"
- app:layout_constraintGuide_percent="0.5"
+ android:id="@+id/begin_guide"
+ android:orientation="vertical"
+ app:layout_constraintGuide_begin="0dp"/>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/end_guide"
+ android:orientation="vertical"
+ app:layout_constraintGuide_end="0dp"
+ />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/center_left"
android:orientation="vertical" />
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/center_right"
+ android:orientation="vertical" />
+
+ <androidx.constraintlayout.widget.Barrier
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/barrier"
+ app:barrierDirection="start"
+ app:constraint_referenced_ids="statusIcons,privacy_container" />
+
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:layout_width="wrap_content"
@@ -44,18 +71,25 @@
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
android:singleLine="true"
+ android:textDirection="locale"
android:textAppearance="@style/TextAppearance.QS.Status"
+ android:transformPivotX="0sp"
+ android:transformPivotY="20sp"
+ android:scaleX="1"
+ android:scaleY="1"
/>
- <com.android.systemui.statusbar.policy.DateView
+ <com.android.systemui.statusbar.policy.VariableDateView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="start|center_vertical"
android:gravity="center_vertical"
android:singleLine="true"
+ android:textDirection="locale"
android:textAppearance="@style/TextAppearance.QS.Status"
- app:datePattern="@string/abbrev_wday_month_day_no_year_alarm"
+ app:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
+ app:shortDatePattern="@string/abbrev_month_day_no_year"
/>
<include
@@ -81,7 +115,7 @@
app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
android:paddingEnd="@dimen/signal_cluster_battery_padding"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toEndOf="@id/carrier_group"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="@id/clock"
@@ -92,8 +126,9 @@
<com.android.systemui.battery.BatteryMeterView
android:id="@+id/batteryRemainingIcon"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+ app:layout_constrainedWidth="true"
app:textAppearance="@style/TextAppearance.QS.Status"
app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="parent"
@@ -104,13 +139,18 @@
<FrameLayout
android:id="@+id/privacy_container"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
android:gravity="center"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/end_guide"
app:layout_constraintTop_toTopOf="@id/date"
app:layout_constraintBottom_toBottomOf="@id/date"
>
<include layout="@layout/ongoing_privacy_chip"/>
</FrameLayout>
-</androidx.constraintlayout.motion.widget.MotionLayout>
\ No newline at end of file
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:id="@+id/space"
+ />
+</com.android.systemui.util.NoRemeasureMotionLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 022a6b2..0cafa35 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -553,7 +553,7 @@
<dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
<dimen name="qs_panel_elevation">4dp</dimen>
<dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen>
- <dimen name="qs_panel_padding_top">48dp</dimen>
+ <dimen name="qs_panel_padding_top">80dp</dimen>
<dimen name="qs_data_usage_text_size">14sp</dimen>
<dimen name="qs_data_usage_usage_text_size">36sp</dimen>
@@ -564,6 +564,8 @@
<dimen name="qs_footer_icon_size">20dp</dimen>
<dimen name="qs_header_row_min_height">48dp</dimen>
+ <dimen name="qs_header_non_clickable_element_height">24dp</dimen>
+
<dimen name="qs_footer_padding">20dp</dimen>
<dimen name="qs_security_footer_height">88dp</dimen>
<dimen name="qs_security_footer_single_line_height">48dp</dimen>
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index 0e83326..0fac76d 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -25,20 +25,109 @@
<KeyFrameSet>
<!-- These positions are to prevent visual movement of @id/date -->
<KeyPosition
- app:keyPositionType="pathRelative"
+ app:keyPositionType="deltaRelative"
app:percentX="0"
+ app:percentY="0"
app:framePosition="49"
+ app:percentWidth="1"
+ app:percentHeight="1"
+ app:curveFit="linear"
app:motionTarget="@id/date" />
<KeyPosition
- app:keyPositionType="pathRelative"
+ app:keyPositionType="deltaRelative"
app:percentX="1"
+ app:percentY="0.51"
app:framePosition="51"
+ app:percentWidth="1"
+ app:percentHeight="1"
+ app:curveFit="linear"
app:motionTarget="@id/date" />
<KeyAttribute
app:motionTarget="@id/date"
- app:framePosition="50"
+ app:framePosition="30"
android:alpha="0"
/>
+ <KeyAttribute
+ app:motionTarget="@id/date"
+ app:framePosition="70"
+ android:alpha="0"
+ />
+ <KeyPosition
+ app:keyPositionType="pathRelative"
+ app:percentX="0"
+ app:percentY="0"
+ app:framePosition="0"
+ app:curveFit="linear"
+ app:motionTarget="@id/statusIcons" />
+ <KeyPosition
+ app:keyPositionType="pathRelative"
+ app:percentX="0"
+ app:percentY="0"
+ app:framePosition="50"
+ app:curveFit="linear"
+ app:motionTarget="@id/statusIcons" />
+ <KeyPosition
+ app:keyPositionType="deltaRelative"
+ app:percentX="1"
+ app:percentY="0.51"
+ app:framePosition="51"
+ app:curveFit="linear"
+ app:motionTarget="@id/statusIcons" />
+ <KeyAttribute
+ app:motionTarget="@id/statusIcons"
+ app:framePosition="30"
+ android:alpha="0"
+ />
+ <KeyAttribute
+ app:motionTarget="@id/statusIcons"
+ app:framePosition="70"
+ android:alpha="0"
+ />
+ <KeyPosition
+ app:keyPositionType="deltaRelative"
+ app:percentX="0"
+ app:percentY="0"
+ app:framePosition="50"
+ app:percentWidth="1"
+ app:percentHeight="1"
+ app:curveFit="linear"
+ app:motionTarget="@id/batteryRemainingIcon" />
+ <KeyPosition
+ app:keyPositionType="deltaRelative"
+ app:percentX="1"
+ app:percentY="0.51"
+ app:framePosition="51"
+ app:percentWidth="1"
+ app:percentHeight="1"
+ app:curveFit="linear"
+ app:motionTarget="@id/batteryRemainingIcon" />
+ <KeyAttribute
+ app:motionTarget="@id/batteryRemainingIcon"
+ app:framePosition="30"
+ android:alpha="0"
+ />
+ <KeyAttribute
+ app:motionTarget="@id/batteryRemainingIcon"
+ app:framePosition="70"
+ android:alpha="0"
+ />
+ <KeyPosition
+ app:motionTarget="@id/carrier_group"
+ app:percentX="1"
+ app:percentY="0.51"
+ app:framePosition="51"
+ app:percentWidth="1"
+ app:percentHeight="1"
+ app:curveFit="linear"
+ app:keyPositionType="deltaRelative" />
+ <KeyAttribute
+ app:motionTarget="@id/carrier_group"
+ app:framePosition="0"
+ android:alpha="0" />
+ <KeyAttribute
+ app:motionTarget="@id/carrier_group"
+ app:framePosition="70"
+ android:alpha="0" />
</KeyFrameSet>
</Transition>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 8909051..cdbf8ab 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -29,7 +29,12 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/date"
+ app:layout_constraintHorizontal_bias="0"
/>
+ <Transform
+ android:scaleX="1"
+ android:scaleY="1"
+ />
</Constraint>
<Constraint
@@ -47,9 +52,38 @@
<Constraint
android:id="@+id/carrier_group">
+ <Layout
+ app:layout_constraintWidth_min="48dp"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constrainedWidth="true"
+ android:layout_gravity="end|center_vertical"
+ android:layout_marginStart="8dp"
+ app:layout_constraintStart_toEndOf="@id/date"
+ app:layout_constraintEnd_toStartOf="@id/statusIcons"
+ app:layout_constraintTop_toTopOf="@id/clock"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
<PropertySet
android:alpha="1"
- app:customFloatValue="1"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/statusIcons">
+ <Layout
+ app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintStart_toEndOf="@id/carrier_group"
+ app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+ app:layout_constraintTop_toTopOf="@id/clock"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ <PropertySet
+ android:alpha="1"
/>
</Constraint>
@@ -64,6 +98,9 @@
app:layout_constraintTop_toTopOf="@id/clock"
app:layout_constraintBottom_toBottomOf="parent"
/>
+ <PropertySet
+ android:alpha="1"
+ />
</Constraint>
<Constraint
@@ -75,6 +112,7 @@
app:layout_constraintTop_toTopOf="@id/date"
app:layout_constraintBottom_toBottomOf="@id/date"
app:layout_constraintStart_toEndOf="@id/batteryRemainingIcon"
+ app:layout_constraintHorizontal_bias="1"
/>
</Constraint>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index c5b4c5d..ee0c4fb6 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -26,22 +26,27 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="0dp"
- app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintStart_toStartOf="@id/begin_guide"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/date"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
/>
+ <Transform
+ android:scaleX="1"
+ android:scaleY="1"
+ />
</Constraint>
<Constraint
android:id="@+id/date">
<Layout
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="0dp"
+ app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/clock"
- app:layout_constraintEnd_toStartOf="@id/carrier_group"
+ app:layout_constraintEnd_toStartOf="@id/barrier"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0"
@@ -50,17 +55,41 @@
<Constraint
android:id="@+id/statusIcons">
+ <Layout
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintStart_toEndOf="@id/date"
+ app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+ app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
</Constraint>
<Constraint
- android:id="@+id/batteryRemainingIcon" >
+ android:id="@+id/batteryRemainingIcon">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintStart_toEndOf="@id/statusIcons"
+ app:layout_constraintEnd_toEndOf="@id/end_guide"
+ app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
</Constraint>
<Constraint
android:id="@+id/carrier_group">
- <CustomAttribute
- app:attributeName="alpha"
- app:customFloatValue="0"
+ <Layout
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ />
+ <PropertySet
+ android:alpha="0"
/>
</Constraint>
@@ -69,9 +98,11 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="0dp"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/date"
+ app:layout_constraintEnd_toEndOf="@id/end_guide"
app:layout_constraintTop_toTopOf="@id/date"
app:layout_constraintBottom_toBottomOf="@id/date"
+ app:layout_constraintHorizontal_bias="1"
/>
</Constraint>
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
new file mode 100644
index 0000000..f39e6bd
--- /dev/null
+++ b/packages/SystemUI/res/xml/qs_header_new.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<ConstraintSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/qs_header_constraint"
+>
+
+ <Constraint
+ android:id="@+id/privacy_container">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintEnd_toEndOf="@id/end_guide"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/carrier_group"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/clock">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/privacy_container"
+ app:layout_constraintBottom_toTopOf="@id/date"
+ app:layout_constraintEnd_toStartOf="@id/carrier_group"
+ app:layout_constraintHorizontal_bias="0"
+ />
+ <Transform
+ android:scaleX="2.4"
+ android:scaleY="2.4"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/date">
+ <Layout
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/space"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/clock"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/carrier_group">
+ <Layout
+ app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+ android:minHeight="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintWidth_min="48dp"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintStart_toEndOf="@id/clock"
+ app:layout_constraintTop_toBottomOf="@id/privacy_container"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
+ />
+ <PropertySet
+ android:alpha="1"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/statusIcons">
+ <Layout
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintStart_toEndOf="@id/space"
+ app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+ app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/batteryRemainingIcon">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintStart_toEndOf="@id/statusIcons"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ </Constraint>
+
+
+ <Constraint
+ android:id="@id/space">
+ <Layout
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintStart_toEndOf="@id/date"
+ app:layout_constraintEnd_toStartOf="@id/statusIcons"
+ />
+ </Constraint>
+</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index b4646ae..a8a526a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -29,6 +29,7 @@
interface ParcelableFlag<T> : Flag<T>, Parcelable {
val default: T
+ val overridden: Boolean
override fun describeContents() = 0
}
@@ -52,7 +53,8 @@
data class BooleanFlag @JvmOverloads constructor(
override val id: Int,
override val default: Boolean = false,
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<Boolean> {
companion object {
@@ -65,12 +67,16 @@
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
- default = parcel.readBoolean()
+ default = parcel.readBoolean(),
+ teamfood = parcel.readBoolean(),
+ overridden = parcel.readBoolean()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeBoolean(default)
+ parcel.writeBoolean(teamfood)
+ parcel.writeBoolean(overridden)
}
}
@@ -100,7 +106,8 @@
data class StringFlag @JvmOverloads constructor(
override val id: Int,
override val default: String = "",
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<String> {
companion object {
@JvmField
@@ -130,7 +137,8 @@
data class IntFlag @JvmOverloads constructor(
override val id: Int,
override val default: Int = 0,
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<Int> {
companion object {
@@ -161,7 +169,8 @@
data class LongFlag @JvmOverloads constructor(
override val id: Int,
override val default: Long = 0,
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<Long> {
companion object {
@@ -186,7 +195,8 @@
data class FloatFlag @JvmOverloads constructor(
override val id: Int,
override val default: Float = 0f,
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<Float> {
companion object {
@@ -217,7 +227,8 @@
data class DoubleFlag @JvmOverloads constructor(
override val id: Int,
override val default: Double = 0.0,
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<Double> {
companion object {
@@ -237,4 +248,4 @@
parcel.writeInt(id)
parcel.writeDouble(default)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index 26e40e1..d172690 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -64,20 +64,29 @@
intent.setPackage(RECEIVING_PACKAGE)
return CallbackToFutureAdapter.getFuture {
- completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> ->
- context.sendOrderedBroadcast(intent, null,
- object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val extras: Bundle? = getResultExtras(false)
- val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
- extras?.getParcelableArrayList(EXTRA_FLAGS)
- if (listOfFlags != null) {
- completer.set(listOfFlags)
- } else {
- completer.setException(NoFlagResultsException())
- }
+ completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> ->
+ context.sendOrderedBroadcast(
+ intent,
+ null,
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val extras: Bundle? = getResultExtras(false)
+ val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
+ extras?.getParcelableArrayList(
+ EXTRA_FLAGS, ParcelableFlag::class.java
+ )
+ if (listOfFlags != null) {
+ completer.set(listOfFlags)
+ } else {
+ completer.setException(NoFlagResultsException())
}
- }, null, Activity.RESULT_OK, "extra data", null)
+ }
+ },
+ null,
+ Activity.RESULT_OK,
+ "extra data",
+ null
+ )
"QueryingFlags"
}
}
@@ -152,7 +161,11 @@
}
val parts = uri.pathSegments
val idStr = parts[parts.size - 1]
- val id = try { idStr.toInt() } catch (e: NumberFormatException) { return }
+ val id = try {
+ idStr.toInt()
+ } catch (e: NumberFormatException) {
+ return
+ }
clearCacheAction?.accept(id)
dispatchListenersAndMaybeRestart(id, onSettingsChangedAction)
}
@@ -188,4 +201,5 @@
}
class NoFlagResultsException : Exception(
- "SystemUI failed to communicate its flags back successfully")
\ No newline at end of file
+ "SystemUI failed to communicate its flags back successfully"
+)
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
index e22386e..b4955d2 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
@@ -257,6 +257,8 @@
)
}
+ private val glyphFilter: GlyphCallback? = null // Add text animation tweak here.
+
/**
* Set text style with an optional animation.
*
@@ -288,6 +290,7 @@
delay = delay,
onAnimationEnd = onAnimationEnd
)
+ textAnimator?.glyphFilter = glyphFilter
} else {
// when the text animator is set, update its start values
onTextAnimatorInitialized = Runnable {
@@ -301,6 +304,7 @@
delay = delay,
onAnimationEnd = onAnimationEnd
)
+ textAnimator?.glyphFilter = glyphFilter
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
index 3361015..ade89af 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
@@ -22,12 +22,14 @@
import android.animation.ValueAnimator
import android.graphics.Canvas
import android.graphics.Typeface
+import android.graphics.fonts.Font
import android.text.Layout
import android.util.SparseArray
private const val TAG_WGHT = "wght"
private const val DEFAULT_ANIMATION_DURATION: Long = 300
+typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
/**
* This class provides text animation between two styles.
*
@@ -74,6 +76,59 @@
})
}
+ sealed class PositionedGlyph {
+
+ /**
+ * Mutable X coordinate of the glyph position relative from drawing offset.
+ */
+ var x: Float = 0f
+
+ /**
+ * Mutable Y coordinate of the glyph position relative from the baseline.
+ */
+ var y: Float = 0f
+
+ /**
+ * Mutable text size of the glyph in pixels.
+ */
+ var textSize: Float = 0f
+
+ /**
+ * Mutable color of the glyph.
+ */
+ var color: Int = 0
+
+ /**
+ * Immutable character offset in the text that the current font run start.
+ */
+ abstract var runStart: Int
+ protected set
+
+ /**
+ * Immutable run length of the font run.
+ */
+ abstract var runLength: Int
+ protected set
+
+ /**
+ * Immutable glyph index of the font run.
+ */
+ abstract var glyphIndex: Int
+ protected set
+
+ /**
+ * Immutable font instance for this font run.
+ */
+ abstract var font: Font
+ protected set
+
+ /**
+ * Immutable glyph ID for this glyph.
+ */
+ abstract var glyphId: Int
+ protected set
+ }
+
private val typefaceCache = SparseArray<Typeface?>()
fun updateLayout(layout: Layout) {
@@ -84,6 +139,57 @@
return animator.isRunning
}
+ /**
+ * GlyphFilter applied just before drawing to canvas for tweaking positions and text size.
+ *
+ * This callback is called for each glyphs just before drawing the glyphs. This function will
+ * be called with the intrinsic position, size, color, glyph ID and font instance. You can
+ * mutate the position, size and color for tweaking animations.
+ * Do not keep the reference of passed glyph object. The interpolator reuses that object for
+ * avoiding object allocations.
+ *
+ * Details:
+ * The text is drawn with font run units. The font run is a text segment that draws with the
+ * same font. The {@code runStart} and {@code runLimit} is a range of the font run in the text
+ * that current glyph is in. Once the font run is determined, the system will convert characters
+ * into glyph IDs. The {@code glyphId} is the glyph identifier in the font and
+ * {@code glyphIndex} is the offset of the converted glyph array. Please note that the
+ * {@code glyphIndex} is not a character index, because the character will not be converted to
+ * glyph one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
+ * composed from multiple characters.
+ *
+ * Here is an example of font runs: "fin. 終わり"
+ *
+ * Characters : f i n . _ 終 わ り
+ * Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A
+ * Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf -->
+ * runStart = 0, runLength = 5 runStart = 5, runLength = 3
+ * Glyph IDs : 194 48 7 8 4367 1039 1002
+ * Glyph Index: 0 1 2 3 0 1 2
+ *
+ * In this example, the "fi" is converted into ligature form, thus the single glyph ID is
+ * assigned for two characters, f and i.
+ *
+ * Example:
+ * ```
+ * private val glyphFilter: GlyphCallback = { glyph, progress ->
+ * val index = glyph.runStart
+ * val i = glyph.glyphIndex
+ * val moveAmount = 1.3f
+ * val sign = (-1 + 2 * ((i + index) % 2))
+ * val turnProgress = if (progress < .5f) progress / 0.5f else (1.0f - progress) / 0.5f
+ *
+ * // You can modify (x, y) coordinates, textSize and color during animation.
+ * glyph.textSize += glyph.textSize * sign * moveAmount * turnProgress
+ * glyph.y += glyph.y * sign * moveAmount * turnProgress
+ * glyph.x += glyph.x * sign * moveAmount * turnProgress
+ * }
+ * ```
+ */
+ var glyphFilter: GlyphCallback?
+ get() = textInterpolator.glyphFilter
+ set(value) { textInterpolator.glyphFilter = value }
+
fun draw(c: Canvas) = textInterpolator.draw(c)
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
index 5d5797c..20dbe29 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
@@ -89,8 +89,11 @@
private var lines = listOf<Line>()
private val fontInterpolator = FontInterpolator()
- // Recycling object for glyph drawing. Will be extended for the longest font run if needed.
- private val tmpDrawPaint = TextPaint()
+ // Recycling object for glyph drawing and tweaking.
+ private val tmpPaint = TextPaint()
+ private val tmpPaintForGlyph by lazy { TextPaint() }
+ private val tmpGlyph by lazy { MutablePositionedGlyph() }
+ // Will be extended for the longest font run if needed.
private var tmpPositionArray = FloatArray(20)
/**
@@ -206,8 +209,8 @@
} else if (progress == 1f) {
basePaint.set(targetPaint)
} else {
- lerp(basePaint, targetPaint, progress, tmpDrawPaint)
- basePaint.set(tmpDrawPaint)
+ lerp(basePaint, targetPaint, progress, tmpPaint)
+ basePaint.set(tmpPaint)
}
lines.forEach { line ->
@@ -231,7 +234,7 @@
* @param canvas a canvas.
*/
fun draw(canvas: Canvas) {
- lerp(basePaint, targetPaint, progress, tmpDrawPaint)
+ lerp(basePaint, targetPaint, progress, tmpPaint)
lines.forEachIndexed { lineNo, line ->
line.runs.forEach { run ->
canvas.save()
@@ -241,7 +244,7 @@
canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
run.fontRuns.forEach { fontRun ->
- drawFontRun(canvas, run, fontRun, tmpDrawPaint)
+ drawFontRun(canvas, run, fontRun, tmpPaint)
}
} finally {
canvas.restore()
@@ -330,24 +333,82 @@
}
}
+ private class MutablePositionedGlyph : TextAnimator.PositionedGlyph() {
+ override var runStart: Int = 0
+ public set
+ override var runLength: Int = 0
+ public set
+ override var glyphIndex: Int = 0
+ public set
+ override lateinit var font: Font
+ public set
+ override var glyphId: Int = 0
+ public set
+ }
+
+ var glyphFilter: GlyphCallback? = null
+
// Draws single font run.
private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) {
var arrayIndex = 0
+ val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress)
+
+ val glyphFilter = glyphFilter
+ if (glyphFilter == null) {
+ for (i in run.start until run.end) {
+ tmpPositionArray[arrayIndex++] =
+ MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
+ tmpPositionArray[arrayIndex++] =
+ MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+ }
+ c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint)
+ return
+ }
+
+ tmpGlyph.font = font
+ tmpGlyph.runStart = run.start
+ tmpGlyph.runLength = run.end - run.start
+
+ tmpPaintForGlyph.set(paint)
+ var prevStart = run.start
+
for (i in run.start until run.end) {
- tmpPositionArray[arrayIndex++] =
- MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
- tmpPositionArray[arrayIndex++] =
- MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+ tmpGlyph.glyphId = line.glyphIds[i]
+ tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
+ tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+ tmpGlyph.textSize = paint.textSize
+ tmpGlyph.color = paint.color
+
+ glyphFilter(tmpGlyph, progress)
+
+ if (tmpGlyph.textSize != paint.textSize || tmpGlyph.color != paint.color) {
+ tmpPaintForGlyph.textSize = tmpGlyph.textSize
+ tmpPaintForGlyph.color = tmpGlyph.color
+
+ c.drawGlyphs(
+ line.glyphIds,
+ prevStart,
+ tmpPositionArray,
+ 0,
+ i - prevStart,
+ font,
+ tmpPaintForGlyph)
+ prevStart = i
+ arrayIndex = 0
+ }
+
+ tmpPositionArray[arrayIndex++] = tmpGlyph.x
+ tmpPositionArray[arrayIndex++] = tmpGlyph.y
}
c.drawGlyphs(
line.glyphIds,
- run.start,
+ prevStart,
tmpPositionArray,
0,
- run.length,
- fontInterpolator.lerp(run.baseFont, run.targetFont, progress),
- paint)
+ run.end - prevStart,
+ font,
+ tmpPaintForGlyph)
}
private fun updatePositionsAndFonts(
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 24fcf15..e9ca0fd 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -96,7 +96,6 @@
.setSplitScreen(mWMComponent.getSplitScreen())
.setOneHanded(mWMComponent.getOneHanded())
.setBubbles(mWMComponent.getBubbles())
- .setShellCommandHandler(mWMComponent.getShellCommandHandler())
.setTaskViewFactory(mWMComponent.getTaskViewFactory())
.setTransitions(mWMComponent.getTransitions())
.setStartingSurface(mWMComponent.getStartingSurface())
@@ -112,7 +111,6 @@
.setSplitScreen(Optional.ofNullable(null))
.setOneHanded(Optional.ofNullable(null))
.setBubbles(Optional.ofNullable(null))
- .setShellCommandHandler(Optional.ofNullable(null))
.setTaskViewFactory(Optional.ofNullable(null))
.setTransitions(new ShellTransitions() {})
.setDisplayAreaHelper(Optional.ofNullable(null))
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
index cccd3a4..81da8023 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -36,7 +36,7 @@
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.PanelViewController
+import com.android.systemui.shade.PanelViewController
import com.android.systemui.statusbar.policy.KeyguardStateController
import java.util.concurrent.Executor
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index fd7680f..029cabb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -37,7 +37,6 @@
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.unfold.UnfoldLatencyTracker;
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
-import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
@@ -96,9 +95,6 @@
Builder setTaskViewFactory(Optional<TaskViewFactory> t);
@BindsInstance
- Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump);
-
- @BindsInstance
Builder setTransitions(ShellTransitions t);
@BindsInstance
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index e4c0325..78a45f9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -23,8 +23,8 @@
import com.android.systemui.SystemUIInitializerFactory;
import com.android.systemui.tv.TvWMComponent;
-import com.android.wm.shell.ShellCommandHandler;
-import com.android.wm.shell.ShellInit;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
@@ -75,17 +75,24 @@
* Initializes all the WMShell components before starting any of the SystemUI components.
*/
default void init() {
- getShellInit().init();
+ // TODO(238217847): To be removed once the dependencies are inverted and ShellController can
+ // inject these classes directly, otherwise, it's currently needed to ensure that these
+ // classes are created and set on the controller before onInit() is called
+ getShellInit();
+ getShellCommandHandler();
+ getShell().onInit();
}
@WMSingleton
+ ShellInterface getShell();
+
+ // TODO(238217847): To be removed once ShellController can inject ShellInit directly
+ @WMSingleton
ShellInit getShellInit();
+ // TODO(238217847): To be removed once ShellController can inject ShellCommandHandler directly
@WMSingleton
- Optional<ShellCommandHandler> getShellCommandHandler();
-
- @WMSingleton
- ShellInterface getShell();
+ ShellCommandHandler getShellCommandHandler();
@WMSingleton
Optional<OneHanded> getOneHanded();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
index b9436f9..9c22dc6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
@@ -25,7 +25,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
import com.android.systemui.dreams.touch.DreamTouchHandler;
-import com.android.systemui.statusbar.phone.PanelViewController;
+import com.android.systemui.shade.PanelViewController;
import com.android.wm.shell.animation.FlingAnimationUtils;
import javax.inject.Named;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 49b3908..c5221cd 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -32,7 +32,6 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -197,7 +196,7 @@
/** Specific override for Boolean flags that checks against the teamfood list.*/
private boolean readFlagValue(int id, boolean defaultValue) {
- Boolean result = readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
+ Boolean result = readBooleanFlagOverride(id);
// Only check for teamfood if the default is false.
if (!defaultValue && result == null && id != Flags.TEAMFOOD.getId()) {
if (mAllFlags.containsKey(id) && mAllFlags.get(id).getTeamfood()) {
@@ -208,6 +207,10 @@
return result == null ? defaultValue : result;
}
+ private Boolean readBooleanFlagOverride(int id) {
+ return readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
+ }
+
@NonNull
private <T> T readFlagValue(int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
requireNonNull(defaultValue, "defaultValue");
@@ -407,11 +410,18 @@
@Nullable
private ParcelableFlag<?> toParcelableFlag(Flag<?> f) {
if (f instanceof BooleanFlag) {
- return new BooleanFlag(f.getId(), isEnabled((BooleanFlag) f), f.getTeamfood());
+ return new BooleanFlag(
+ f.getId(),
+ isEnabled((BooleanFlag) f),
+ f.getTeamfood(),
+ readBooleanFlagOverride(f.getId()) != null);
}
if (f instanceof ResourceBooleanFlag) {
return new BooleanFlag(
- f.getId(), isEnabled((ResourceBooleanFlag) f), f.getTeamfood());
+ f.getId(),
+ isEnabled((ResourceBooleanFlag) f),
+ f.getTeamfood(),
+ readBooleanFlagOverride(f.getId()) != null);
}
if (f instanceof DeviceConfigBooleanFlag) {
return new BooleanFlag(
@@ -420,7 +430,10 @@
if (f instanceof SysPropBooleanFlag) {
// TODO(b/223379190): Teamfood not supported for sysprop flags yet.
return new BooleanFlag(
- f.getId(), isEnabled((SysPropBooleanFlag) f), false);
+ f.getId(),
+ ((SysPropBooleanFlag) f).getDefault(),
+ false,
+ !mSystemProperties.get(((SysPropBooleanFlag) f).getName()).isEmpty());
}
// TODO: add support for other flag types.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 245ea21..65711ed 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -63,6 +63,9 @@
public static final BooleanFlag REMOVE_UNRANKED_NOTIFICATIONS =
new BooleanFlag(109, false);
+ public static final BooleanFlag FSI_REQUIRES_KEYGUARD =
+ new BooleanFlag(110, false, true);
+
/***************************************/
// 200 - keyguard/lockscreen
@@ -116,7 +119,7 @@
new BooleanFlag(500, true);
public static final BooleanFlag COMBINED_QS_HEADERS =
- new BooleanFlag(501, false);
+ new BooleanFlag(501, false, true);
public static final ResourceBooleanFlag PEOPLE_TILE =
new ResourceBooleanFlag(502, R.bool.flag_conversations);
@@ -130,7 +133,7 @@
@Deprecated
public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, true);
- public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false);
+ public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false, true);
public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
@@ -142,6 +145,9 @@
public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
+ public static final BooleanFlag STATUS_BAR_LETTERBOX_APPEARANCE =
+ new BooleanFlag(603, false);
+
/***************************************/
// 700 - dialer/calls
public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt
new file mode 100644
index 0000000..e360ec2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shade
+
+import androidx.constraintlayout.widget.ConstraintSet
+
+typealias ConstraintChange = ConstraintSet.() -> Unit
+
+operator fun ConstraintChange?.plus(other: ConstraintChange?): ConstraintChange? {
+ // Prevent wrapping
+ if (this == null) return other
+ if (other == null) return this
+ else return {
+ this@plus()
+ other()
+ }
+}
+
+/**
+ * Contains all changes that need to be performed to the different [ConstraintSet] in
+ * [LargeScreenShadeHeaderController].
+ */
+data class ConstraintsChanges(
+ val qqsConstraintsChanges: ConstraintChange? = null,
+ val qsConstraintsChanges: ConstraintChange? = null,
+ val largeScreenConstraintsChanges: ConstraintChange? = null
+) {
+ operator fun plus(other: ConstraintsChanges) = ConstraintsChanges(
+ qqsConstraintsChanges + other.qqsConstraintsChanges,
+ qsConstraintsChanges + other.qsConstraintsChanges,
+ largeScreenConstraintsChanges + other.largeScreenConstraintsChanges
+ )
+}
+
+/**
+ * Determines [ConstraintChanges] for [LargeScreenShadeHeaderController] based on configurations.
+ *
+ * Given that the number of different scenarios is not that large, having specific methods instead
+ * of a full map between state and [ConstraintSet] was preferred.
+ */
+interface CombinedShadeHeadersConstraintManager {
+ /**
+ * Changes for when the visibility of the privacy chip changes
+ */
+ fun privacyChipVisibilityConstraints(visible: Boolean): ConstraintsChanges
+
+ /**
+ * Changes for situations with no top center cutout (there may be a corner cutout)
+ */
+ fun emptyCutoutConstraints(): ConstraintsChanges
+
+ /**
+ * Changes to incorporate side insets due to rounded corners/corner cutouts
+ */
+ fun edgesGuidelinesConstraints(
+ cutoutStart: Int,
+ paddingStart: Int,
+ cutoutEnd: Int,
+ paddingEnd: Int
+ ): ConstraintsChanges
+
+ /**
+ * Changes for situations with top center cutout (in this case, there are no corner cutouts).
+ */
+ fun centerCutoutConstraints(rtl: Boolean, offsetFromEdge: Int): ConstraintsChanges
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
new file mode 100644
index 0000000..4063af3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+
+/**
+ * Standard implementation of [CombinedShadeHeadersConstraintManager].
+ */
+@CentralSurfacesComponent.CentralSurfacesScope
+object CombinedShadeHeadersConstraintManagerImpl : CombinedShadeHeadersConstraintManager {
+
+ override fun privacyChipVisibilityConstraints(visible: Boolean): ConstraintsChanges {
+ val constraintAlpha = if (visible) 0f else 1f
+ return ConstraintsChanges(
+ qqsConstraintsChanges = {
+ setAlpha(R.id.statusIcons, constraintAlpha)
+ setAlpha(R.id.batteryRemainingIcon, constraintAlpha)
+ }
+ )
+ }
+
+ override fun emptyCutoutConstraints(): ConstraintsChanges {
+ return ConstraintsChanges(
+ qqsConstraintsChanges = {
+ connect(R.id.date, ConstraintSet.END, R.id.barrier, ConstraintSet.START)
+ createBarrier(
+ R.id.barrier,
+ ConstraintSet.START,
+ 0,
+ R.id.statusIcons,
+ R.id.privacy_container
+ )
+ connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
+ connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
+ constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
+ }
+ )
+ }
+
+ override fun edgesGuidelinesConstraints(
+ cutoutStart: Int,
+ paddingStart: Int,
+ cutoutEnd: Int,
+ paddingEnd: Int
+ ): ConstraintsChanges {
+ val change: ConstraintChange = {
+ setGuidelineBegin(R.id.begin_guide, Math.max(cutoutStart - paddingStart, 0))
+ setGuidelineEnd(R.id.end_guide, Math.max(cutoutEnd - paddingEnd, 0))
+ }
+ return ConstraintsChanges(
+ qqsConstraintsChanges = change,
+ qsConstraintsChanges = change
+ )
+ }
+
+ override fun centerCutoutConstraints(rtl: Boolean, offsetFromEdge: Int): ConstraintsChanges {
+ val centerStart = if (!rtl) R.id.center_left else R.id.center_right
+ val centerEnd = if (!rtl) R.id.center_right else R.id.center_left
+ // Use guidelines to block the center cutout area.
+ return ConstraintsChanges(
+ qqsConstraintsChanges = {
+ setGuidelineBegin(centerStart, offsetFromEdge)
+ setGuidelineEnd(centerEnd, offsetFromEdge)
+ connect(R.id.date, ConstraintSet.END, centerStart, ConstraintSet.START)
+ connect(
+ R.id.statusIcons,
+ ConstraintSet.START,
+ centerEnd,
+ ConstraintSet.END
+ )
+ connect(
+ R.id.privacy_container,
+ ConstraintSet.START,
+ centerEnd,
+ ConstraintSet.END
+ )
+ constrainWidth(R.id.statusIcons, 0)
+ },
+ qsConstraintsChanges = {
+ setGuidelineBegin(centerStart, offsetFromEdge)
+ setGuidelineEnd(centerEnd, offsetFromEdge)
+ connect(
+ R.id.privacy_container,
+ ConstraintSet.START,
+ centerEnd,
+ ConstraintSet.END
+ )
+ }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
new file mode 100644
index 0000000..5793105
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.annotation.IdRes
+import android.app.StatusBarManager
+import android.content.res.Configuration
+import android.os.Trace
+import android.os.Trace.TRACE_TAG_APP
+import android.util.Pair
+import android.view.View
+import android.view.WindowInsets
+import android.widget.TextView
+import androidx.annotation.VisibleForTesting
+import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.settingslib.Utils
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.ChipVisibilityListener
+import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.qs.carrier.QSCarrierGroup
+import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
+import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.VariableDateView
+import com.android.systemui.statusbar.policy.VariableDateViewController
+import com.android.systemui.util.ViewController
+import java.io.PrintWriter
+import javax.inject.Inject
+import javax.inject.Named
+
+/**
+ * Controller for QS header on Large Screen width (large screen + landscape).
+ *
+ * Additionally, this serves as the staging ground for the combined QS headers. A single
+ * [MotionLayout] that changes constraints depending on the configuration and can animate the
+ * expansion of the headers in small screen portrait.
+ *
+ * [header] will be a [MotionLayout] if [Flags.COMBINED_QS_HEADERS] is enabled. In this case, the
+ * [MotionLayout] has 2 transitions:
+ * * [HEADER_TRANSITION_ID]: [QQS_HEADER_CONSTRAINT] <-> [QS_HEADER_CONSTRAINT] for portrait
+ * handheld device configuration.
+ * * [LARGE_SCREEN_HEADER_TRANSITION_ID]: [LARGE_SCREEN_HEADER_CONSTRAINT] (to itself) for all
+ * other configurations
+ */
+@CentralSurfacesScope
+class LargeScreenShadeHeaderController @Inject constructor(
+ @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View,
+ private val statusBarIconController: StatusBarIconController,
+ private val privacyIconsController: HeaderPrivacyIconsController,
+ private val insetsProvider: StatusBarContentInsetsProvider,
+ private val configurationController: ConfigurationController,
+ private val variableDateViewControllerFactory: VariableDateViewController.Factory,
+ @Named(LARGE_SCREEN_BATTERY_CONTROLLER)
+ private val batteryMeterViewController: BatteryMeterViewController,
+ private val dumpManager: DumpManager,
+ private val featureFlags: FeatureFlags,
+ private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
+ private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager
+) : ViewController<View>(header), Dumpable {
+
+ companion object {
+ /** IDs for transitions and constraints for the [MotionLayout]. These are only used when
+ * [Flags.COMBINED_QS_HEADERS] is enabled.
+ */
+ @VisibleForTesting
+ internal val HEADER_TRANSITION_ID = R.id.header_transition
+ @VisibleForTesting
+ internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
+ @VisibleForTesting
+ internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
+ @VisibleForTesting
+ internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
+ @VisibleForTesting
+ internal val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint
+
+ private fun Int.stateToString() = when (this) {
+ QQS_HEADER_CONSTRAINT -> "QQS Header"
+ QS_HEADER_CONSTRAINT -> "QS Header"
+ LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
+ else -> "Unknown state"
+ }
+ }
+
+ init {
+ loadConstraints()
+ }
+
+ private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
+
+ private lateinit var iconManager: StatusBarIconController.TintedIconManager
+ private lateinit var carrierIconSlots: List<String>
+ private lateinit var qsCarrierGroupController: QSCarrierGroupController
+
+ private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
+ private val clock: TextView = header.findViewById(R.id.clock)
+ private val date: TextView = header.findViewById(R.id.date)
+ private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
+ private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
+
+ private var cutoutLeft = 0
+ private var cutoutRight = 0
+ private var roundedCorners = 0
+ private var lastInsets: WindowInsets? = null
+
+ private var qsDisabled = false
+ private var visible = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ updateListeners()
+ }
+
+ /**
+ * Whether the QQS/QS part of the shade is visible. This is particularly important in
+ * Lockscreen, as the shade is visible but QS is not.
+ */
+ var qsVisible = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ onShadeExpandedChanged()
+ }
+
+ /**
+ * Whether we are in a configuration with large screen width. In this case, the header is a
+ * single line.
+ */
+ var largeScreenActive = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ onHeaderStateChanged()
+ }
+
+ /**
+ * Expansion fraction of the QQS/QS shade. This is not the expansion between QQS <-> QS.
+ */
+ var shadeExpandedFraction = -1f
+ set(value) {
+ if (visible && field != value) {
+ header.alpha = ShadeInterpolation.getContentAlpha(value)
+ field = value
+ }
+ }
+
+ /**
+ * Expansion fraction of the QQS <-> QS animation.
+ */
+ var qsExpandedFraction = -1f
+ set(value) {
+ if (visible && field != value) {
+ field = value
+ updatePosition()
+ }
+ }
+
+ /**
+ * Current scroll of QS.
+ */
+ var qsScrollY = 0
+ set(value) {
+ if (field != value) {
+ field = value
+ updateScrollY()
+ }
+ }
+
+ private val insetListener = View.OnApplyWindowInsetsListener { view, insets ->
+ updateConstraintsForInsets(view as MotionLayout, insets)
+ lastInsets = WindowInsets(insets)
+
+ view.onApplyWindowInsets(insets)
+ }
+
+ private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
+ override fun onChipVisibilityRefreshed(visible: Boolean) {
+ if (header is MotionLayout) {
+ // If the privacy chip is visible, we hide the status icons and battery remaining
+ // icon, only in QQS.
+ val update = combinedShadeHeadersConstraintManager
+ .privacyChipVisibilityConstraints(visible)
+ header.updateAllConstraints(update)
+ }
+ }
+ }
+
+ private val configurationControllerListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ if (header !is MotionLayout) {
+ val left = header.resources.getDimensionPixelSize(
+ R.dimen.large_screen_shade_header_left_padding
+ )
+ header.setPadding(
+ left,
+ header.paddingTop,
+ header.paddingRight,
+ header.paddingBottom
+ )
+ }
+ }
+
+ override fun onDensityOrFontScaleChanged() {
+ clock.setTextAppearance(R.style.TextAppearance_QS_Status)
+ date.setTextAppearance(R.style.TextAppearance_QS_Status)
+ qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
+ if (header is MotionLayout) {
+ loadConstraints()
+ lastInsets?.let { updateConstraintsForInsets(header, it) }
+ }
+ updateResources()
+ }
+ }
+
+ override fun onInit() {
+ if (header is MotionLayout) {
+ variableDateViewControllerFactory.create(date as VariableDateView).init()
+ }
+ batteryMeterViewController.init()
+
+ // battery settings same as in QS icons
+ batteryMeterViewController.ignoreTunerUpdates()
+ batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+
+ iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
+ iconManager.setTint(
+ Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
+ )
+
+ carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
+ listOf(
+ header.context.getString(com.android.internal.R.string.status_bar_no_calling),
+ header.context.getString(com.android.internal.R.string.status_bar_call_strength)
+ )
+ } else {
+ listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
+ }
+ qsCarrierGroupController = qsCarrierGroupControllerBuilder
+ .setQSCarrierGroup(qsCarrierGroup)
+ .build()
+ }
+
+ override fun onViewAttached() {
+ privacyIconsController.chipVisibilityListener = chipVisibilityListener
+ if (header is MotionLayout) {
+ header.setOnApplyWindowInsetsListener(insetListener)
+ clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
+ val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
+ v.pivotX = newPivot
+ }
+ }
+
+ dumpManager.registerDumpable(this)
+ configurationController.addCallback(configurationControllerListener)
+
+ updateVisibility()
+ updateTransition()
+ }
+
+ override fun onViewDetached() {
+ privacyIconsController.chipVisibilityListener = null
+ dumpManager.unregisterDumpable(this::class.java.simpleName)
+ configurationController.removeCallback(configurationControllerListener)
+ }
+
+ fun disable(state1: Int, state2: Int, animate: Boolean) {
+ val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
+ if (disabled == qsDisabled) return
+ qsDisabled = disabled
+ updateVisibility()
+ }
+
+ private fun loadConstraints() {
+ if (header is MotionLayout) {
+ // Use resources.getXml instead of passing the resource id due to bug b/205018300
+ header.getConstraintSet(QQS_HEADER_CONSTRAINT)
+ .load(context, resources.getXml(R.xml.qqs_header))
+ val qsConstraints = if (featureFlags.isEnabled(Flags.NEW_HEADER)) {
+ R.xml.qs_header_new
+ } else {
+ R.xml.qs_header
+ }
+ header.getConstraintSet(QS_HEADER_CONSTRAINT)
+ .load(context, resources.getXml(qsConstraints))
+ header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
+ .load(context, resources.getXml(R.xml.large_screen_shade_header))
+ }
+ }
+
+ private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
+ val cutout = insets.displayCutout
+
+ val sbInsets: Pair<Int, Int> = insetsProvider.getStatusBarContentInsetsForCurrentRotation()
+ cutoutLeft = sbInsets.first
+ cutoutRight = sbInsets.second
+ val hasCornerCutout: Boolean = insetsProvider.currentRotationHasCornerCutout()
+ updateQQSPaddings()
+ // Set these guides as the left/right limits for content that lives in the top row, using
+ // cutoutLeft and cutoutRight
+ var changes = combinedShadeHeadersConstraintManager
+ .edgesGuidelinesConstraints(
+ if (view.isLayoutRtl) cutoutRight else cutoutLeft,
+ header.paddingStart,
+ if (view.isLayoutRtl) cutoutLeft else cutoutRight,
+ header.paddingEnd
+ )
+
+ if (cutout != null) {
+ val topCutout = cutout.boundingRectTop
+ if (topCutout.isEmpty || hasCornerCutout) {
+ changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
+ } else {
+ changes += combinedShadeHeadersConstraintManager.centerCutoutConstraints(
+ view.isLayoutRtl,
+ (view.width - view.paddingLeft - view.paddingRight - topCutout.width()) / 2
+ )
+ }
+ } else {
+ changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
+ }
+
+ view.updateAllConstraints(changes)
+ }
+
+ private fun updateScrollY() {
+ if (!largeScreenActive && combinedHeaders) {
+ header.scrollY = qsScrollY
+ }
+ }
+
+ private fun onShadeExpandedChanged() {
+ if (qsVisible) {
+ privacyIconsController.startListening()
+ } else {
+ privacyIconsController.stopListening()
+ }
+ updateVisibility()
+ updatePosition()
+ }
+
+ private fun onHeaderStateChanged() {
+ if (largeScreenActive || combinedHeaders) {
+ privacyIconsController.onParentVisible()
+ } else {
+ privacyIconsController.onParentInvisible()
+ }
+ updateVisibility()
+ updateTransition()
+ }
+
+ /**
+ * If not using [combinedHeaders] this should only be visible on large screen. Else, it should
+ * be visible any time the QQS/QS shade is open.
+ */
+ private fun updateVisibility() {
+ val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) {
+ View.GONE
+ } else if (qsVisible) {
+ View.VISIBLE
+ } else {
+ View.INVISIBLE
+ }
+ if (header.visibility != visibility) {
+ header.visibility = visibility
+ visible = visibility == View.VISIBLE
+ }
+ }
+
+ private fun updateTransition() {
+ if (!combinedHeaders) {
+ return
+ }
+ header as MotionLayout
+ if (largeScreenActive) {
+ header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
+ header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header)
+ } else {
+ header.setTransition(HEADER_TRANSITION_ID)
+ header.transitionToStart()
+ updatePosition()
+ updateScrollY()
+ }
+ }
+
+ private fun updatePosition() {
+ if (header is MotionLayout && !largeScreenActive && visible) {
+ Trace.instantForTrack(
+ TRACE_TAG_APP,
+ "LargeScreenHeaderController - updatePosition",
+ "position: $qsExpandedFraction"
+ )
+ header.progress = qsExpandedFraction
+ }
+ }
+
+ private fun updateListeners() {
+ qsCarrierGroupController.setListening(visible)
+ if (visible) {
+ updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
+ qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
+ statusBarIconController.addIconGroup(iconManager)
+ } else {
+ qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
+ statusBarIconController.removeIconGroup(iconManager)
+ }
+ }
+
+ private fun updateSingleCarrier(singleCarrier: Boolean) {
+ if (singleCarrier) {
+ iconContainer.removeIgnoredSlots(carrierIconSlots)
+ } else {
+ iconContainer.addIgnoredSlots(carrierIconSlots)
+ }
+ }
+
+ private fun updateResources() {
+ roundedCorners = resources.getDimensionPixelSize(R.dimen.rounded_corner_content_padding)
+ val padding = resources.getDimensionPixelSize(R.dimen.qs_panel_padding)
+ header.setPadding(padding, header.paddingTop, padding, header.paddingBottom)
+ updateQQSPaddings()
+ }
+
+ private fun updateQQSPaddings() {
+ if (header is MotionLayout) {
+ val clockPaddingStart = resources
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_starting_padding)
+ val clockPaddingEnd = resources
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_end_padding)
+ clock.setPaddingRelative(
+ clockPaddingStart,
+ clock.paddingTop,
+ clockPaddingEnd,
+ clock.paddingBottom
+ )
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("visible: $visible")
+ pw.println("shadeExpanded: $qsVisible")
+ pw.println("shadeExpandedFraction: $shadeExpandedFraction")
+ pw.println("active: $largeScreenActive")
+ pw.println("qsExpandedFraction: $qsExpandedFraction")
+ pw.println("qsScrollY: $qsScrollY")
+ if (combinedHeaders) {
+ header as MotionLayout
+ pw.println("currentState: ${header.currentState.stateToString()}")
+ }
+ }
+
+ private fun MotionLayout.updateConstraints(@IdRes state: Int, update: ConstraintChange) {
+ val constraints = getConstraintSet(state)
+ constraints.update()
+ updateState(state, constraints)
+ }
+
+ /**
+ * Updates the [ConstraintSet] for the case of combined headers.
+ *
+ * Only non-`null` changes are applied to reduce the number of rebuilding in the [MotionLayout].
+ */
+ private fun MotionLayout.updateAllConstraints(updates: ConstraintsChanges) {
+ if (updates.qqsConstraintsChanges != null) {
+ updateConstraints(QQS_HEADER_CONSTRAINT, updates.qqsConstraintsChanges)
+ }
+ if (updates.qsConstraintsChanges != null) {
+ updateConstraints(QS_HEADER_CONSTRAINT, updates.qsConstraintsChanges)
+ }
+ if (updates.largeScreenConstraintsChanges != null) {
+ updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index e0997ff..9818af3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -25,7 +25,6 @@
import android.util.AttributeSet;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.PanelView;
import com.android.systemui.statusbar.phone.TapAgainView;
public class NotificationPanelView extends PanelView {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index bab92ba..99d0fe9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -189,12 +189,9 @@
import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
-import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.PanelView;
-import com.android.systemui.statusbar.phone.PanelViewController;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -1038,6 +1035,7 @@
}
mTapAgainViewController.init();
+ mLargeScreenShadeHeaderController.init();
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
controller.setup(mNotificationContainerParent));
@@ -1143,7 +1141,7 @@
SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
- mLargeScreenShadeHeaderController.setActive(mUseLargeScreenShadeHeader);
+ mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
mAmbientState.setStackTopMargin(topMargin);
mNotificationsQSContainerController.updateResources();
@@ -1716,10 +1714,17 @@
/**
* Animate QS closing by flinging it.
* If QS is expanded, it will collapse into QQS and stop.
+ * If in split shade, it will collapse the whole shade.
*
* @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
*/
public void animateCloseQs(boolean animateAway) {
+ if (mSplitShadeEnabled) {
+ collapsePanel(
+ /* animate= */true, /* delayed= */false, /* speedUpFactor= */1.0f);
+ return;
+ }
+
if (mQsExpansionAnimator != null) {
if (!mQsAnimatorExpand) {
return;
@@ -2417,7 +2422,7 @@
: getExpandedFraction();
mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
- mLargeScreenShadeHeaderController.setShadeExpanded(mQsVisible);
+ mLargeScreenShadeHeaderController.setQsVisible(mQsVisible);
}
private void onStackYChanged(boolean shouldAnimate) {
@@ -3392,19 +3397,11 @@
return mQsExpanded;
}
- public boolean isQsDetailShowing() {
- return mQs.isShowingDetail();
- }
-
/** Returns whether the QS customizer is currently active. */
public boolean isQsCustomizing() {
return mQs.isCustomizing();
}
- public void closeQsDetail() {
- mQs.closeDetail();
- }
-
/** Close the QS customizer if it is open. */
public void closeQsCustomizer() {
mQs.closeCustomizer();
@@ -3748,6 +3745,8 @@
final float dozeAmount = dozing ? 1 : 0;
mStatusBarStateController.setAndInstrumentDozeAmount(mView, dozeAmount, animate);
+
+ updateKeyguardStatusViewAlignment(animate);
}
public void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index 133711e..7dc9dc7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -3,6 +3,7 @@
per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com
per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com
+per-file *ShadeHeader* = kozynski@google.com, asc@google.com
per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
rename to packages/SystemUI/src/com/android/systemui/shade/PanelView.java
index 45dc943..1082967 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import android.content.Context;
import android.content.res.Configuration;
@@ -22,6 +22,10 @@
import android.view.MotionEvent;
import android.widget.FrameLayout;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+
public abstract class PanelView extends FrameLayout {
public static final boolean DEBUG = false;
public static final String TAG = PanelView.class.getSimpleName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
rename to packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index 3a85a3e..229acf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
@@ -60,7 +60,14 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.phone.BounceInterpolator;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.time.SystemClock;
@@ -388,7 +395,7 @@
return Math.abs(yDiff) >= Math.abs(xDiff);
}
- protected void startExpandMotion(float newX, float newY, boolean startTracking,
+ public void startExpandMotion(float newX, float newY, boolean startTracking,
float expandedHeight) {
if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index c4947ca..0c6a91f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -59,4 +59,7 @@
fun removeUnrankedNotifs(): Boolean =
featureFlags.isEnabled(Flags.REMOVE_UNRANKED_NOTIFICATIONS)
+
+ fun fullScreenIntentRequiresKeyguard(): Boolean =
+ featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 535dc6e..a72b381 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -41,6 +41,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.util.ArrayList;
import java.util.List;
@@ -58,6 +59,7 @@
private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>();
private final StatusBarStateController mStatusBarStateController;
+ private final KeyguardStateController mKeyguardStateController;
private final NotificationFilter mNotificationFilter;
private final ContentResolver mContentResolver;
private final PowerManager mPowerManager;
@@ -82,6 +84,7 @@
NotificationFilter notificationFilter,
BatteryController batteryController,
StatusBarStateController statusBarStateController,
+ KeyguardStateController keyguardStateController,
HeadsUpManager headsUpManager,
NotificationInterruptLogger logger,
@Main Handler mainHandler,
@@ -94,6 +97,7 @@
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mNotificationFilter = notificationFilter;
mStatusBarStateController = statusBarStateController;
+ mKeyguardStateController = keyguardStateController;
mHeadsUpManager = headsUpManager;
mLogger = logger;
mFlags = flags;
@@ -228,6 +232,28 @@
return false;
}
+ // Check whether FSI requires the keyguard to be showing.
+ if (mFlags.fullScreenIntentRequiresKeyguard()) {
+
+ // If notification won't HUN and keyguard is showing, launch the FSI.
+ if (mKeyguardStateController.isShowing()) {
+ if (mKeyguardStateController.isOccluded()) {
+ mLogger.logFullscreen(entry, "Expected not to HUN while keyguard occluded");
+ } else {
+ // Likely LOCKED_SHADE, but launch FSI anyway
+ mLogger.logFullscreen(entry, "Keyguard is showing and not occluded");
+ }
+ return true;
+ }
+
+ // Detect the case determined by b/231322873 to launch FSI while device is in use,
+ // as blocked by the correct implementation, and report the event.
+ final int uid = entry.getSbn().getUid();
+ android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard");
+ mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+ return false;
+ }
+
// If the notification won't HUN for some other reason (DND/snooze/etc), launch FSI.
mLogger.logFullscreen(entry, "Expected not to HUN");
return true;
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 2d6d846..98404eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -3325,11 +3325,7 @@
return true;
}
if (mNotificationPanelViewController.isQsExpanded()) {
- if (mNotificationPanelViewController.isQsDetailShowing()) {
- mNotificationPanelViewController.closeQsDetail();
- } else {
mNotificationPanelViewController.animateCloseQs(false /* animateAway */);
- }
return true;
}
if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt
deleted file mode 100644
index 84c8700..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone
-
-import android.app.StatusBarManager
-import android.content.res.Configuration
-import android.view.View
-import android.widget.TextView
-import androidx.constraintlayout.motion.widget.MotionLayout
-import com.android.settingslib.Utils
-import com.android.systemui.Dumpable
-import com.android.systemui.FontSizeUtils
-import com.android.systemui.R
-import com.android.systemui.animation.ShadeInterpolation
-import com.android.systemui.battery.BatteryMeterView
-import com.android.systemui.battery.BatteryMeterViewController
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.qs.ChipVisibilityListener
-import com.android.systemui.qs.HeaderPrivacyIconsController
-import com.android.systemui.qs.carrier.QSCarrierGroup
-import com.android.systemui.qs.carrier.QSCarrierGroupController
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
-import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
-import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
-import com.android.systemui.statusbar.policy.ConfigurationController
-import java.io.PrintWriter
-import javax.inject.Inject
-import javax.inject.Named
-
-@CentralSurfacesScope
-class LargeScreenShadeHeaderController @Inject constructor(
- @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View,
- private val statusBarIconController: StatusBarIconController,
- private val privacyIconsController: HeaderPrivacyIconsController,
- private val configurationController: ConfigurationController,
- qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
- featureFlags: FeatureFlags,
- @Named(LARGE_SCREEN_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController,
- dumpManager: DumpManager
-) : Dumpable {
-
- companion object {
- private val HEADER_TRANSITION_ID = R.id.header_transition
- private val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
- private val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
- private val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
- private val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint
-
- private fun Int.stateToString() = when (this) {
- QQS_HEADER_CONSTRAINT -> "QQS Header"
- QS_HEADER_CONSTRAINT -> "QS Header"
- LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
- else -> "Unknown state"
- }
- }
-
- private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
- private val iconManager: StatusBarIconController.TintedIconManager
- private val iconContainer: StatusIconContainer
- private val carrierIconSlots: List<String>
- private val qsCarrierGroupController: QSCarrierGroupController
- private val clock: TextView = header.findViewById(R.id.clock)
- private val date: TextView = header.findViewById(R.id.date)
- private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
-
- private var qsDisabled = false
-
- private var visible = false
- set(value) {
- if (field == value) {
- return
- }
- field = value
- updateListeners()
- }
-
- var shadeExpanded = false
- set(value) {
- if (field == value) {
- return
- }
- field = value
- onShadeExpandedChanged()
- }
-
- var active = false
- set(value) {
- if (field == value) {
- return
- }
- field = value
- onHeaderStateChanged()
- }
-
- var shadeExpandedFraction = -1f
- set(value) {
- if (visible && field != value) {
- header.alpha = ShadeInterpolation.getContentAlpha(value)
- field = value
- }
- }
-
- var qsExpandedFraction = -1f
- set(value) {
- if (visible && field != value) {
- field = value
- updateVisibility()
- updatePosition()
- }
- }
-
- var qsScrollY = 0
- set(value) {
- if (field != value) {
- field = value
- updateScrollY()
- }
- }
-
- private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
- override fun onChipVisibilityRefreshed(visible: Boolean) {
- if (header is MotionLayout) {
- val state = header.getConstraintSet(QQS_HEADER_CONSTRAINT).apply {
- setAlpha(R.id.statusIcons, if (visible) 0f else 1f)
- setAlpha(R.id.batteryRemainingIcon, if (visible) 0f else 1f)
- }
- header.updateState(QQS_HEADER_CONSTRAINT, state)
- }
- }
- }
-
- init {
- if (header is MotionLayout) {
- val context = header.context
- val resources = header.resources
- header.getConstraintSet(QQS_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.qqs_header))
- header.getConstraintSet(QS_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.qs_header))
- header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.large_screen_shade_header))
- privacyIconsController.chipVisibilityListener = chipVisibilityListener
- }
-
- bindConfigurationListener()
-
- batteryMeterViewController.init()
- val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
-
- // battery settings same as in QS icons
- batteryMeterViewController.ignoreTunerUpdates()
- batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
-
- iconContainer = header.findViewById(R.id.statusIcons)
- iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
- iconManager.setTint(Utils.getColorAttrDefaultColor(header.context,
- android.R.attr.textColorPrimary))
-
- carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
- listOf(
- header.context.getString(com.android.internal.R.string.status_bar_no_calling),
- header.context.getString(com.android.internal.R.string.status_bar_call_strength)
- )
- } else {
- listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
- }
- qsCarrierGroupController = qsCarrierGroupControllerBuilder
- .setQSCarrierGroup(header.findViewById(R.id.carrier_group))
- .build()
-
- dumpManager.registerDumpable(this)
-
- updateVisibility()
- updateConstraints()
- }
-
- fun disable(state1: Int, state2: Int, animate: Boolean) {
- val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
- if (disabled == qsDisabled) return
- qsDisabled = disabled
- updateVisibility()
- }
-
- private fun updateScrollY() {
- if (!active && combinedHeaders) {
- header.scrollY = qsScrollY
- }
- }
-
- private fun bindConfigurationListener() {
- val listener = object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- val left = header.resources.getDimensionPixelSize(
- R.dimen.large_screen_shade_header_left_padding)
- header.setPadding(
- left, header.paddingTop, header.paddingRight, header.paddingBottom)
- }
- override fun onDensityOrFontScaleChanged() {
- val qsStatusStyle = R.style.TextAppearance_QS_Status
- FontSizeUtils.updateFontSizeFromStyle(clock, qsStatusStyle)
- FontSizeUtils.updateFontSizeFromStyle(date, qsStatusStyle)
- qsCarrierGroup.updateTextAppearance(qsStatusStyle)
- }
- }
- configurationController.addCallback(listener)
- }
-
- private fun onShadeExpandedChanged() {
- if (shadeExpanded) {
- privacyIconsController.startListening()
- } else {
- privacyIconsController.stopListening()
- }
- updateVisibility()
- updatePosition()
- }
-
- private fun onHeaderStateChanged() {
- if (active || combinedHeaders) {
- privacyIconsController.onParentVisible()
- } else {
- privacyIconsController.onParentInvisible()
- }
- updateVisibility()
- updateConstraints()
- }
-
- private fun updateVisibility() {
- val visibility = if (!active && !combinedHeaders || qsDisabled) {
- View.GONE
- } else if (shadeExpanded) {
- View.VISIBLE
- } else {
- View.INVISIBLE
- }
- if (header.visibility != visibility) {
- header.visibility = visibility
- visible = visibility == View.VISIBLE
- }
- }
-
- private fun updateConstraints() {
- if (!combinedHeaders) {
- return
- }
- header as MotionLayout
- if (active) {
- header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
- } else {
- header.setTransition(HEADER_TRANSITION_ID)
- header.transitionToStart()
- updatePosition()
- updateScrollY()
- }
- }
-
- private fun updatePosition() {
- if (header is MotionLayout && !active && visible) {
- header.setProgress(qsExpandedFraction)
- }
- }
-
- private fun updateListeners() {
- qsCarrierGroupController.setListening(visible)
- if (visible) {
- updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
- qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
- statusBarIconController.addIconGroup(iconManager)
- } else {
- qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
- statusBarIconController.removeIconGroup(iconManager)
- }
- }
-
- private fun updateSingleCarrier(singleCarrier: Boolean) {
- if (singleCarrier) {
- iconContainer.removeIgnoredSlots(carrierIconSlots)
- } else {
- iconContainer.addIgnoredSlots(carrierIconSlots)
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("visible: $visible")
- pw.println("shadeExpanded: $shadeExpanded")
- pw.println("shadeExpandedFraction: $shadeExpandedFraction")
- pw.println("active: $active")
- pw.println("qsExpandedFraction: $qsExpandedFraction")
- pw.println("qsScrollY: $qsScrollY")
- if (combinedHeaders) {
- header as MotionLayout
- pw.println("currentState: ${header.currentState.stateToString()}")
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
new file mode 100644
index 0000000..f5ba399
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.graphics.Rect
+import android.view.View
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.END_SIDE_CONTENT
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.START_SIDE_CONTENT
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope
+import com.android.systemui.util.boundsOnScreen
+import javax.inject.Inject
+import javax.inject.Named
+
+/** Provides various bounds within the status bar. */
+@StatusBarFragmentScope
+class StatusBarBoundsProvider
+@Inject
+constructor(
+ private val changeListeners: Set<@JvmSuppressWildcards BoundsChangeListener>,
+ @Named(START_SIDE_CONTENT) private val startSideContent: View,
+ @Named(END_SIDE_CONTENT) private val endSideContent: View,
+) : StatusBarFragmentComponent.Startable {
+
+ interface BoundsChangeListener {
+ fun onStatusBarBoundsChanged()
+ }
+
+ private var previousBounds =
+ BoundsPair(start = startSideContent.boundsOnScreen, end = endSideContent.boundsOnScreen)
+
+ private val layoutListener =
+ View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+ val newBounds = BoundsPair(start = visibleStartSideBounds, end = visibleEndSideBounds)
+ if (previousBounds != newBounds) {
+ previousBounds = newBounds
+ changeListeners.forEach { it.onStatusBarBoundsChanged() }
+ }
+ }
+
+ override fun start() {
+ startSideContent.addOnLayoutChangeListener(layoutListener)
+ endSideContent.addOnLayoutChangeListener(layoutListener)
+ }
+
+ override fun stop() {
+ startSideContent.removeOnLayoutChangeListener(layoutListener)
+ endSideContent.removeOnLayoutChangeListener(layoutListener)
+ }
+
+ /**
+ * Returns the bounds of the end side of the status bar that are visible to the user. The end
+ * side is right when in LTR and is left when in RTL.
+ *
+ * Note that even though the layout might be larger, here we only return the bounds for what is
+ * visible to the user.
+ *
+ * The end side of the status bar contains the multi-user switcher and status icons such as
+ * wi-fi, battery, etc
+ */
+ val visibleEndSideBounds: Rect
+ get() = endSideContent.boundsOnScreen
+
+ /**
+ * Returns the bounds of the start side of the status bar that are visible to the user. The
+ * start side is left when in LTR and is right when in RTL.
+ *
+ * Note that even though the layout might be larger, here we only return the bounds for what is
+ * visible to the user.
+ *
+ * The start side of the status bar contains the operator name, clock, on-going call chip, and
+ * notifications.
+ */
+ val visibleStartSideBounds: Rect
+ get() = startSideContent.boundsOnScreen
+}
+
+private data class BoundsPair(val start: Rect, val end: Rect)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index f5462bc..c850d4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -123,7 +123,7 @@
val point = Point()
context.display.getRealSize(point)
- return topBounds.left <= 0 || topBounds.right >= point.y
+ return topBounds.left <= 0 || topBounds.right >= point.x
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 4bbd69b..4c23931 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -399,7 +399,8 @@
return true;
}
- if (sbn.getNotification().fullScreenIntent != null) {
+ if (sbn.getNotification().fullScreenIntent != null
+ && !mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()) {
// we don't allow head-up on the lockscreen (unless there's a
// "showWhenLocked" activity currently showing) if
// the potential HUN has a fullscreen intent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index 84b2797..64b04e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -22,6 +22,7 @@
import com.android.keyguard.LockIconViewController;
import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.shade.LargeScreenShadeHeaderController;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -35,7 +36,6 @@
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutListContainerModule;
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController;
import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarterModule;
import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 41df8e3..b607391 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -30,10 +30,13 @@
import com.android.systemui.biometrics.AuthRippleView;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.privacy.OngoingPrivacyChip;
+import com.android.systemui.shade.CombinedShadeHeadersConstraintManager;
+import com.android.systemui.shade.CombinedShadeHeadersConstraintManagerImpl;
import com.android.systemui.shade.NotificationPanelView;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.NotificationShadeWindowView;
@@ -178,6 +181,14 @@
/** */
@Provides
@CentralSurfacesComponent.CentralSurfacesScope
+ public static CombinedShadeHeadersConstraintManager
+ provideCombinedShadeHeadersConstraintManager() {
+ return CombinedShadeHeadersConstraintManagerImpl.INSTANCE;
+ }
+
+ /** */
+ @Provides
+ @CentralSurfacesComponent.CentralSurfacesScope
public static OngoingPrivacyChip getSplitShadeOngoingPrivacyChip(
@Named(LARGE_SCREEN_SHADE_HEADER) View header) {
return header.findViewById(R.id.privacy_chip);
@@ -269,7 +280,8 @@
CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
- @Main Executor mainExecutor
+ @Main Executor mainExecutor,
+ DumpManager dumpManager
) {
return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory,
ongoingCallController,
@@ -289,7 +301,8 @@
collapsedStatusBarFragmentLogger,
operatorNameViewControllerFactory,
secureSettings,
- mainExecutor);
+ mainExecutor,
+ dumpManager);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 39bf92a..0848729 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -34,6 +34,8 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
@@ -43,9 +45,11 @@
import androidx.annotation.VisibleForTesting;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -66,6 +70,7 @@
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
@@ -76,9 +81,12 @@
import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
import com.android.systemui.util.settings.SecureSettings;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -89,7 +97,7 @@
@SuppressLint("ValidFragment")
public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks,
StatusBarStateController.StateListener,
- SystemStatusAnimationCallback {
+ SystemStatusAnimationCallback, Dumpable {
public static final String TAG = "CollapsedStatusBarFragment";
private static final String EXTRA_PANEL_STATE = "panel_state";
@@ -124,8 +132,10 @@
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
+ private final DumpManager mDumpManager;
private List<String> mBlockedIcons = new ArrayList<>();
+ private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
private SignalCallback mSignalCallback = new SignalCallback() {
@Override
@@ -185,7 +195,8 @@
CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
- @Main Executor mainExecutor
+ @Main Executor mainExecutor,
+ DumpManager dumpManager
) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
@@ -206,6 +217,7 @@
mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
mSecureSettings = secureSettings;
mMainExecutor = mainExecutor;
+ mDumpManager = dumpManager;
}
@Override
@@ -217,8 +229,15 @@
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
+ mDumpManager.registerDumpable(getClass().getSimpleName(), this);
mStatusBarFragmentComponent = mStatusBarFragmentComponentFactory.create(this);
mStatusBarFragmentComponent.init();
+ mStartableStates.clear();
+ for (Startable startable : mStatusBarFragmentComponent.getStartables()) {
+ mStartableStates.put(startable, Startable.State.STARTING);
+ startable.start();
+ mStartableStates.put(startable, Startable.State.STARTED);
+ }
mStatusBar = (PhoneStatusBarView) view;
View contents = mStatusBar.findViewById(R.id.status_bar_contents);
@@ -321,6 +340,13 @@
}
mCarrierConfigTracker.removeCallback(mCarrierConfigCallback);
mCarrierConfigTracker.removeDataSubscriptionChangedListener(mDefaultDataListener);
+
+ for (Startable startable : mStatusBarFragmentComponent.getStartables()) {
+ mStartableStates.put(startable, Startable.State.STOPPING);
+ startable.stop();
+ mStartableStates.put(startable, Startable.State.STOPPED);
+ }
+ mDumpManager.unregisterDumpable(getClass().getSimpleName());
}
/** Initializes views related to the notification icon area. */
@@ -670,4 +696,23 @@
updateStatusBarLocation(left, right);
}
};
+
+ @Override
+ public void dump(PrintWriter printWriter, String[] args) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, /* singleIndent= */" ");
+ StatusBarFragmentComponent component = mStatusBarFragmentComponent;
+ if (component == null) {
+ pw.println("StatusBarFragmentComponent is null");
+ } else {
+ Set<Startable> startables = component.getStartables();
+ pw.println("Startables: " + startables.size());
+ pw.increaseIndent();
+ for (Startable startable : startables) {
+ Startable.State startableState = mStartableStates.getOrDefault(startable,
+ Startable.State.NONE);
+ pw.println(startable + ", state: " + startableState);
+ }
+ pw.decreaseIndent();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
index 6717bc7..d9a5844 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
@@ -23,9 +23,12 @@
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.StatusBarDemoMode;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
+import java.util.Set;
+
import dagger.BindsInstance;
import dagger.Subcomponent;
@@ -41,7 +44,10 @@
* should be included here or in {@link StatusBarFragmentModule}.
*/
-@Subcomponent(modules = {StatusBarFragmentModule.class})
+@Subcomponent(modules = {
+ StatusBarFragmentModule.class,
+ StatusBarStartablesModule.class
+})
@StatusBarFragmentScope
public interface StatusBarFragmentComponent {
/** Simple factory. */
@@ -52,6 +58,18 @@
}
/**
+ * Performs initialization logic after {@link StatusBarFragmentComponent} has been constructed.
+ */
+ interface Startable {
+ void start();
+ void stop();
+
+ enum State {
+ NONE, STARTING, STARTED, STOPPING, STOPPED
+ }
+ }
+
+ /**
* Initialize anything extra for the component. Must be called after the component is created.
*/
default void init() {
@@ -92,4 +110,10 @@
/** */
@StatusBarFragmentScope
PhoneStatusBarTransitions getPhoneStatusBarTransitions();
+
+ /** */
+ Set<Startable> getStartables();
+
+ /** */
+ StatusBarBoundsProvider getBoundsProvider();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 7252dfb..41f1f95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -26,6 +26,7 @@
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
@@ -34,12 +35,14 @@
import com.android.systemui.statusbar.window.StatusBarWindowController;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Named;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.Multibinds;
/** Dagger module for {@link StatusBarFragmentComponent}. */
@Module
@@ -48,6 +51,8 @@
String LIGHTS_OUT_NOTIF_VIEW = "lights_out_notif_view";
String OPERATOR_NAME_VIEW = "operator_name_view";
String OPERATOR_NAME_FRAME_VIEW = "operator_name_frame_view";
+ String START_SIDE_CONTENT = "start_side_content";
+ String END_SIDE_CONTENT = "end_side_content";
/** */
@Provides
@@ -68,6 +73,22 @@
/** */
@Provides
@StatusBarFragmentScope
+ @Named(START_SIDE_CONTENT)
+ static View startSideContent(@RootView PhoneStatusBarView view) {
+ return view.findViewById(R.id.status_bar_start_side_content);
+ }
+
+ /** */
+ @Provides
+ @StatusBarFragmentScope
+ @Named(END_SIDE_CONTENT)
+ static View endSideContent(@RootView PhoneStatusBarView view) {
+ return view.findViewById(R.id.status_bar_end_side_content);
+ }
+
+ /** */
+ @Provides
+ @StatusBarFragmentScope
@Named(LIGHTS_OUT_NOTIF_VIEW)
static View provideLightsOutNotifView(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.notification_lights_out);
@@ -138,4 +159,8 @@
static HeadsUpStatusBarView providesHeasdUpStatusBarView(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.heads_up_status_bar_view);
}
+
+ /** */
+ @Multibinds
+ Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt
new file mode 100644
index 0000000..9003d13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.fragment.dagger
+
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+internal interface StatusBarStartablesModule {
+
+ @Binds
+ @IntoSet
+ fun statusBarBoundsCalculator(
+ statusBarBoundsProvider: StatusBarBoundsProvider
+ ): StatusBarFragmentComponent.Startable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 337ffdf..86e7456 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -155,6 +155,7 @@
.inflate(R.layout.brightness_mirror_container, mStatusBarWindow, false);
mToggleSliderController = setMirrorLayout();
mStatusBarWindow.addView(mBrightnessMirror, index);
+ updateResources();
for (int i = 0; i < mBrightnessMirrorListeners.size(); i++) {
mBrightnessMirrorListeners.valueAt(i).onBrightnessMirrorReinflated(mBrightnessMirror);
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
index 57b3f53..ec7aabb 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -16,7 +16,9 @@
package com.android.systemui.util
+import android.graphics.Rect
import android.util.IndentingPrintWriter
+import android.view.View
import android.view.ViewGroup
import java.io.PrintWriter
@@ -45,4 +47,12 @@
if (this is IndentingPrintWriter) increaseIndent()
block()
if (this is IndentingPrintWriter) decreaseIndent()
-}
\ No newline at end of file
+}
+
+/** Convenience extension property for [View.getBoundsOnScreen]. */
+val View.boundsOnScreen: Rect
+ get() {
+ val bounds = Rect()
+ getBoundsOnScreen(bounds)
+ return bounds
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
new file mode 100644
index 0000000..3095d80
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.Choreographer
+import androidx.constraintlayout.motion.widget.MotionLayout
+
+/**
+ * [MotionLayout] that avoids remeasuring with the same inputs in the same frame.
+ *
+ * This is important when this view is the child of a view that performs more than one measure pass
+ * (e.g. [LinearLayout] or [ConstraintLayout]). In those cases, if this view is measured with the
+ * same inputs in the same frame, we use the last result.
+ */
+class NoRemeasureMotionLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyle: Int = 0
+) : MotionLayout(context, attrs, defStyle) {
+
+ private var lastWidthSpec: Int? = null
+ private var lastHeightSpec: Int? = null
+ private var lastFrame: Long? = null
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ if (
+ lastWidthSpec == widthMeasureSpec &&
+ lastHeightSpec == heightMeasureSpec &&
+ Choreographer.getMainThreadInstance()?.frameTime == lastFrame
+ ) {
+ setMeasuredDimension(measuredWidth, measuredHeight)
+ return
+ }
+ traceSection("NoRemeasureMotionLayout - measure") {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ lastWidthSpec = widthMeasureSpec
+ lastHeightSpec = heightMeasureSpec
+ lastFrame = Choreographer.getMainThreadInstance()?.frameTime
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 83b0022..12597e0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -54,7 +54,6 @@
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
-import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -107,7 +106,6 @@
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
- private final Optional<ShellCommandHandler> mShellCommandHandler;
private final CommandQueue mCommandQueue;
private final ConfigurationController mConfigurationController;
@@ -130,7 +128,6 @@
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
- Optional<ShellCommandHandler> shellCommandHandler,
CommandQueue commandQueue,
ConfigurationController configurationController,
KeyguardStateController keyguardStateController,
@@ -154,7 +151,6 @@
mOneHandedOptional = oneHandedOptional;
mWakefulnessLifecycle = wakefulnessLifecycle;
mProtoTracer = protoTracer;
- mShellCommandHandler = shellCommandHandler;
mUserInfoController = userInfoController;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -325,8 +321,7 @@
@Override
public void dump(PrintWriter pw, String[] args) {
// Handle commands if provided
- if (mShellCommandHandler.isPresent()
- && mShellCommandHandler.get().handleCommand(args, pw)) {
+ if (mShell.handleCommand(args, pw)) {
return;
}
// Handle logging commands if provided
@@ -334,8 +329,7 @@
return;
}
// Dump WMShell stuff here if no commands were handled
- mShellCommandHandler.ifPresent(
- shellCommandHandler -> shellCommandHandler.dump(pw));
+ mShell.dump(pw);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
index e42d537..603cf3b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
@@ -18,6 +18,7 @@
import android.graphics.Bitmap
import android.graphics.Canvas
+import android.graphics.Color
import android.graphics.Typeface
import android.graphics.fonts.Font
import android.graphics.fonts.FontFamily
@@ -194,6 +195,128 @@
assertThat(expected.sameAs(actual)).isTrue()
}
+
+ @Test
+ fun testGlyphCallback_Empty() {
+ val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+ val interp = TextInterpolator(layout).apply {
+ glyphFilter = { glyph, progress ->
+ }
+ }
+ interp.basePaint.set(START_PAINT)
+ interp.onBasePaintModified()
+
+ interp.targetPaint.set(END_PAINT)
+ interp.onTargetPaintModified()
+
+ // Just after created TextInterpolator, it should have 0 progress.
+ val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+ assertThat(expected.sameAs(actual)).isTrue()
+ }
+
+ @Test
+ fun testGlyphCallback_Xcoordinate() {
+ val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+ val interp = TextInterpolator(layout).apply {
+ glyphFilter = { glyph, progress ->
+ glyph.x += 30f
+ }
+ }
+ interp.basePaint.set(START_PAINT)
+ interp.onBasePaintModified()
+
+ interp.targetPaint.set(END_PAINT)
+ interp.onTargetPaintModified()
+
+ // Just after created TextInterpolator, it should have 0 progress.
+ val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+ // The glyph position was modified by callback, so the bitmap should not be the same.
+ // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps.
+ assertThat(expected.sameAs(actual)).isFalse()
+ }
+
+ @Test
+ fun testGlyphCallback_Ycoordinate() {
+ val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+ val interp = TextInterpolator(layout).apply {
+ glyphFilter = { glyph, progress ->
+ glyph.y += 30f
+ }
+ }
+ interp.basePaint.set(START_PAINT)
+ interp.onBasePaintModified()
+
+ interp.targetPaint.set(END_PAINT)
+ interp.onTargetPaintModified()
+
+ // Just after created TextInterpolator, it should have 0 progress.
+ val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+ // The glyph position was modified by callback, so the bitmap should not be the same.
+ // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps.
+ assertThat(expected.sameAs(actual)).isFalse()
+ }
+
+ @Test
+ fun testGlyphCallback_TextSize() {
+ val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+ val interp = TextInterpolator(layout).apply {
+ glyphFilter = { glyph, progress ->
+ glyph.textSize += 10f
+ }
+ }
+ interp.basePaint.set(START_PAINT)
+ interp.onBasePaintModified()
+
+ interp.targetPaint.set(END_PAINT)
+ interp.onTargetPaintModified()
+
+ // Just after created TextInterpolator, it should have 0 progress.
+ val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+ // The glyph position was modified by callback, so the bitmap should not be the same.
+ // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps.
+ assertThat(expected.sameAs(actual)).isFalse()
+ }
+
+ @Test
+ fun testGlyphCallback_Color() {
+ val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+ val interp = TextInterpolator(layout).apply {
+ glyphFilter = { glyph, progress ->
+ glyph.color = Color.RED
+ }
+ }
+ interp.basePaint.set(START_PAINT)
+ interp.onBasePaintModified()
+
+ interp.targetPaint.set(END_PAINT)
+ interp.onTargetPaintModified()
+
+ // Just after created TextInterpolator, it should have 0 progress.
+ val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+ // The glyph position was modified by callback, so the bitmap should not be the same.
+ // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps.
+ assertThat(expected.sameAs(actual)).isFalse()
+ }
}
private fun Layout.toBitmap(width: Int, height: Int) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
new file mode 100644
index 0000000..0ce9056
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
+
+ private lateinit var qqsConstraint: ConstraintSet
+ private lateinit var qsConstraint: ConstraintSet
+ private lateinit var largeScreenConstraint: ConstraintSet
+
+ @Before
+ fun setUp() {
+ qqsConstraint = ConstraintSet().apply {
+ load(context, context.resources.getXml(R.xml.qqs_header))
+ }
+ qsConstraint = ConstraintSet().apply {
+ load(context, context.resources.getXml(R.xml.qs_header_new))
+ }
+ largeScreenConstraint = ConstraintSet().apply {
+ load(context, context.resources.getXml(R.xml.large_screen_shade_header))
+ }
+ }
+
+ @Test
+ fun testEdgeElementsAlignedWithGuide_qqs() {
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(R.id.begin_guide)
+ assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+
+ assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
+ .isEqualTo(R.id.end_guide)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
+ .isEqualTo(1f)
+
+ assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
+ .isEqualTo(R.id.end_guide)
+ assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias)
+ .isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun testClockScale() {
+ with(qqsConstraint.getConstraint(R.id.clock)) {
+ assertThat(transform.scaleX).isEqualTo(1f)
+ assertThat(transform.scaleY).isEqualTo(1f)
+ }
+ with(qsConstraint.getConstraint(R.id.clock)) {
+ assertThat(transform.scaleX).isGreaterThan(1f)
+ assertThat(transform.scaleY).isGreaterThan(1f)
+ }
+ }
+
+ @Test
+ fun testEdgeElementsAlignedWithEdgeOrGuide_qs() {
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
+ assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+
+ assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID)
+ assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0f)
+
+ assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
+ .isEqualTo(PARENT_ID)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
+ .isEqualTo(1f)
+
+ assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
+ .isEqualTo(R.id.end_guide)
+ assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun testEdgeElementsAlignedWithEdge_largeScreen() {
+ with(largeScreenConstraint) {
+ assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
+ assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+
+ assertThat(getConstraint(R.id.privacy_container).layout.endToEnd).isEqualTo(PARENT_ID)
+ assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun testCarrierAlpha() {
+ assertThat(qqsConstraint.getConstraint(R.id.carrier_group).propertySet.alpha).isEqualTo(0f)
+ assertThat(qsConstraint.getConstraint(R.id.carrier_group).propertySet.alpha).isEqualTo(1f)
+ assertThat(largeScreenConstraint.getConstraint(R.id.carrier_group).propertySet.alpha)
+ .isEqualTo(1f)
+ }
+
+ @Test
+ fun testPrivacyChipVisibilityConstraints_notVisible() {
+ val changes = CombinedShadeHeadersConstraintManagerImpl
+ .privacyChipVisibilityConstraints(false)
+ changes()
+
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+ }
+
+ with(largeScreenConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun testPrivacyChipVisibilityConstraints_visible() {
+ val changes = CombinedShadeHeadersConstraintManagerImpl
+ .privacyChipVisibilityConstraints(true)
+ changes()
+
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(0f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(0f)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+ }
+
+ with(largeScreenConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun testEmptyCutoutConstraints() {
+ val changes = CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()
+ changes()
+
+ // QS and Large Screen don't change with cutouts.
+ assertThat(changes.qsConstraintsChanges).isNull()
+ assertThat(changes.largeScreenConstraintsChanges).isNull()
+
+ with(qqsConstraint) {
+ // In this case, the date is constrained on the end by a Barrier determined by either
+ // privacy or statusIcons
+ assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.barrier)
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd).isEqualTo(R.id.date)
+ assertThat(getConstraint(R.id.privacy_container).layout.startToEnd).isEqualTo(R.id.date)
+ assertThat(getConstraint(R.id.barrier).layout.mReferenceIds).asList().containsExactly(
+ R.id.statusIcons,
+ R.id.privacy_container
+ )
+ assertThat(getConstraint(R.id.barrier).layout.mBarrierDirection).isEqualTo(START)
+ }
+ }
+
+ @Test
+ fun testGuidesAreSetInCorrectPosition_largeCutoutSmallerPadding() {
+ val cutoutStart = 100
+ val padding = 10
+ val cutoutEnd = 30
+ val changes = CombinedShadeHeadersConstraintManagerImpl.edgesGuidelinesConstraints(
+ cutoutStart,
+ padding,
+ cutoutEnd,
+ padding
+ )
+ changes()
+
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
+ .isEqualTo(cutoutStart - padding)
+ assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
+ .isEqualTo(cutoutEnd - padding)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
+ .isEqualTo(cutoutStart - padding)
+ assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
+ .isEqualTo(cutoutEnd - padding)
+ }
+
+ assertThat(changes.largeScreenConstraintsChanges).isNull()
+ }
+
+ @Test
+ fun testGuidesAreSetInCorrectPosition_smallCutoutLargerPadding() {
+ val cutoutStart = 5
+ val padding = 10
+ val cutoutEnd = 10
+
+ val changes = CombinedShadeHeadersConstraintManagerImpl.edgesGuidelinesConstraints(
+ cutoutStart,
+ padding,
+ cutoutEnd,
+ padding
+ )
+ changes()
+
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
+ assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
+ assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
+ }
+
+ assertThat(changes.largeScreenConstraintsChanges).isNull()
+ }
+
+ @Test
+ fun testCenterCutoutConstraints_ltr() {
+ val offsetFromEdge = 400
+ val rtl = false
+
+ val changes = CombinedShadeHeadersConstraintManagerImpl
+ .centerCutoutConstraints(rtl, offsetFromEdge)
+ changes()
+
+ // In LTR, center_left is towards the start and center_right is towards the end
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isEqualTo(R.id.center_right)
+ assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+ .isEqualTo(R.id.center_right)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge)
+
+ assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
+
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isNotEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isNotEqualTo(R.id.center_right)
+
+ assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+ .isEqualTo(R.id.center_right)
+ }
+
+ assertThat(changes.largeScreenConstraintsChanges).isNull()
+ }
+
+ @Test
+ fun testCenterCutoutConstraints_rtl() {
+ val offsetFromEdge = 400
+ val rtl = true
+
+ val changes = CombinedShadeHeadersConstraintManagerImpl
+ .centerCutoutConstraints(rtl, offsetFromEdge)
+ changes()
+
+ // In RTL, center_left is towards the end and center_right is towards the start
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_right)
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+ .isEqualTo(R.id.center_left)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge)
+
+ assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
+
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isNotEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isNotEqualTo(R.id.center_right)
+
+ assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+ .isEqualTo(R.id.center_left)
+ }
+
+ assertThat(changes.largeScreenConstraintsChanges).isNull()
+ }
+
+ private operator fun ConstraintsChanges.invoke() {
+ qqsConstraintsChanges?.invoke(qqsConstraint)
+ qsConstraintsChanges?.invoke(qsConstraint)
+ largeScreenConstraintsChanges?.invoke(largeScreenConstraint)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt
new file mode 100644
index 0000000..9b2e085
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ConstraintChangeTest : SysuiTestCase() {
+
+ @Test
+ fun testSumNonNull() {
+ val mock1: ConstraintChange = mock()
+ val mock2: ConstraintChange = mock()
+
+ val constraintSet = ConstraintSet()
+
+ val sum = mock1 + mock2
+ sum?.invoke(constraintSet)
+
+ val inOrder = inOrder(mock1, mock2)
+ inOrder.verify(mock1).invoke(constraintSet)
+ inOrder.verify(mock2).invoke(constraintSet)
+ }
+
+ @Test
+ fun testSumThisNull() {
+ val mock: ConstraintChange = mock()
+ val constraintSet = ConstraintSet()
+
+ val sum = (null as? ConstraintChange?) + mock
+ sum?.invoke(constraintSet)
+
+ verify(mock).invoke(constraintSet)
+ }
+
+ @Test
+ fun testSumThisNull_notWrapped() {
+ val change: ConstraintChange = {}
+
+ val sum = (null as? ConstraintChange?) + change
+ assertThat(sum).isSameInstanceAs(change)
+ }
+
+ @Test
+ fun testSumOtherNull() {
+ val mock: ConstraintChange = mock()
+ val constraintSet = ConstraintSet()
+
+ val sum = mock + (null as? ConstraintChange?)
+ sum?.invoke(constraintSet)
+
+ verify(mock).invoke(constraintSet)
+ }
+
+ @Test
+ fun testSumOtherNull_notWrapped() {
+ val change: ConstraintChange = {}
+
+ val sum = change + (null as? ConstraintChange?)
+ assertThat(sum).isSameInstanceAs(change)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt
new file mode 100644
index 0000000..0abb084
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ConstraintChangesTest : SysuiTestCase() {
+
+ @Test
+ fun testSumWithoutNulls() {
+ val mockQQS1: ConstraintChange = mock()
+ val mockQS1: ConstraintChange = mock()
+ val mockLS1: ConstraintChange = mock()
+ val mockQQS2: ConstraintChange = mock()
+ val mockQS2: ConstraintChange = mock()
+ val mockLS2: ConstraintChange = mock()
+
+ val changes1 = ConstraintsChanges(mockQQS1, mockQS1, mockLS1)
+ val changes2 = ConstraintsChanges(mockQQS2, mockQS2, mockLS2)
+
+ val sum = changes1 + changes2
+
+ val constraintSet = ConstraintSet()
+ sum.qqsConstraintsChanges?.invoke(constraintSet)
+ sum.qsConstraintsChanges?.invoke(constraintSet)
+ sum.largeScreenConstraintsChanges?.invoke(constraintSet)
+
+ val inOrder = inOrder(mockQQS1, mockQS1, mockLS1, mockQQS2, mockQS2, mockLS2)
+
+ inOrder.verify(mockQQS1).invoke(constraintSet)
+ inOrder.verify(mockQQS2).invoke(constraintSet)
+ inOrder.verify(mockQS1).invoke(constraintSet)
+ inOrder.verify(mockQS2).invoke(constraintSet)
+ inOrder.verify(mockLS1).invoke(constraintSet)
+ inOrder.verify(mockLS2).invoke(constraintSet)
+ }
+
+ @Test
+ fun testSumWithSomeNulls() {
+ val mockQQS: ConstraintChange = mock()
+ val mockQS: ConstraintChange = mock()
+
+ val changes1 = ConstraintsChanges(mockQQS, null, null)
+ val changes2 = ConstraintsChanges(null, mockQS, null)
+
+ val sum = changes1 + changes2
+
+ assertThat(sum.qqsConstraintsChanges).isSameInstanceAs(mockQQS)
+ assertThat(sum.qsConstraintsChanges).isSameInstanceAs(mockQS)
+ assertThat(sum.largeScreenConstraintsChanges).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
new file mode 100644
index 0000000..ed1a13b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -0,0 +1,659 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.content.Context
+import android.content.res.Resources
+import android.content.res.XmlResourceParser
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.view.DisplayCutout
+import android.view.View
+import android.view.WindowInsets
+import android.widget.TextView
+import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.ChipVisibilityListener
+import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.qs.carrier.QSCarrierGroup
+import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.VariableDateView
+import com.android.systemui.statusbar.policy.VariableDateViewController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+private val EMPTY_CHANGES = ConstraintsChanges()
+
+/**
+ * Tests for [LargeScreenShadeHeaderController] when [Flags.COMBINED_QS_HEADERS] is `true`.
+ *
+ * Once that flag is removed, this class will be combined with
+ * [LargeScreenShadeHeaderControllerTest].
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var statusIcons: StatusIconContainer
+ @Mock
+ private lateinit var statusBarIconController: StatusBarIconController
+ @Mock
+ private lateinit var qsCarrierGroupController: QSCarrierGroupController
+ @Mock
+ private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
+ @Mock
+ private lateinit var clock: TextView
+ @Mock
+ private lateinit var date: VariableDateView
+ @Mock
+ private lateinit var carrierGroup: QSCarrierGroup
+ @Mock
+ private lateinit var batteryMeterView: BatteryMeterView
+ @Mock
+ private lateinit var batteryMeterViewController: BatteryMeterViewController
+ @Mock
+ private lateinit var privacyIconsController: HeaderPrivacyIconsController
+ @Mock
+ private lateinit var insetsProvider: StatusBarContentInsetsProvider
+ @Mock
+ private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
+ @Mock
+ private lateinit var variableDateViewController: VariableDateViewController
+ @Mock
+ private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var combinedShadeHeadersConstraintManager:
+ CombinedShadeHeadersConstraintManager
+
+ @Mock
+ private lateinit var mockedContext: Context
+ @Mock(answer = Answers.RETURNS_MOCKS)
+ private lateinit var view: MotionLayout
+
+ @Mock
+ private lateinit var qqsConstraints: ConstraintSet
+ @Mock
+ private lateinit var qsConstraints: ConstraintSet
+ @Mock
+ private lateinit var largeScreenConstraints: ConstraintSet
+
+ @JvmField @Rule
+ val mockitoRule = MockitoJUnit.rule()
+ var viewVisibility = View.GONE
+
+ private lateinit var controller: LargeScreenShadeHeaderController
+ private lateinit var carrierIconSlots: List<String>
+ private val configurationController = FakeConfigurationController()
+
+ @Before
+ fun setUp() {
+ whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+ whenever(clock.context).thenReturn(mockedContext)
+
+ whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
+ whenever(date.context).thenReturn(mockedContext)
+ whenever(variableDateViewControllerFactory.create(any()))
+ .thenReturn(variableDateViewController)
+
+ whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
+ whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
+ .thenReturn(batteryMeterView)
+
+ whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
+ whenever(statusIcons.context).thenReturn(context)
+
+ whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
+ .thenReturn(qsCarrierGroupControllerBuilder)
+ whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
+
+ whenever(view.context).thenReturn(context)
+ whenever(view.resources).thenReturn(context.resources)
+ whenever(view.setVisibility(ArgumentMatchers.anyInt())).then {
+ viewVisibility = it.arguments[0] as Int
+ null
+ }
+ whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+
+ whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
+ whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
+
+ setUpDefaultInsets()
+ setUpMotionLayout(view)
+
+ controller = LargeScreenShadeHeaderController(
+ view,
+ statusBarIconController,
+ privacyIconsController,
+ insetsProvider,
+ configurationController,
+ variableDateViewControllerFactory,
+ batteryMeterViewController,
+ dumpManager,
+ featureFlags,
+ qsCarrierGroupControllerBuilder,
+ combinedShadeHeadersConstraintManager
+ )
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ controller.init()
+ carrierIconSlots = listOf(
+ context.getString(com.android.internal.R.string.status_bar_mobile))
+ }
+
+ @Test
+ fun testCorrectConstraints() {
+ val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
+
+ verify(qqsConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
+
+ verify(qsConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header_new)
+
+ verify(largeScreenConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
+ }
+
+ @Test
+ fun testControllersCreatedAndInitialized() {
+ verify(variableDateViewController).init()
+
+ verify(batteryMeterViewController).init()
+ verify(batteryMeterViewController).ignoreTunerUpdates()
+ verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+
+ val inOrder = inOrder(qsCarrierGroupControllerBuilder)
+ inOrder.verify(qsCarrierGroupControllerBuilder).setQSCarrierGroup(carrierGroup)
+ inOrder.verify(qsCarrierGroupControllerBuilder).build()
+ }
+
+ @Test
+ fun testClockPivotLtr() {
+ val width = 200
+ whenever(clock.width).thenReturn(width)
+ whenever(clock.isLayoutRtl).thenReturn(false)
+
+ val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+ verify(clock).addOnLayoutChangeListener(capture(captor))
+
+ captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
+ verify(clock).pivotX = 0f
+ }
+
+ @Test
+ fun testClockPivotRtl() {
+ val width = 200
+ whenever(clock.width).thenReturn(width)
+ whenever(clock.isLayoutRtl).thenReturn(true)
+
+ val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+ verify(clock).addOnLayoutChangeListener(capture(captor))
+
+ captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
+ verify(clock).pivotX = width.toFloat()
+ }
+
+ @Test
+ fun testShadeExpanded_true() {
+ // When shade is expanded, view should be visible regardless of largeScreenActive
+ controller.largeScreenActive = false
+ controller.qsVisible = true
+ assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+
+ controller.largeScreenActive = true
+ assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun testShadeExpanded_false() {
+ // When shade is not expanded, view should be invisible regardless of largeScreenActive
+ controller.largeScreenActive = false
+ controller.qsVisible = false
+ assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+
+ controller.largeScreenActive = true
+ assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ fun testLargeScreenActive_true() {
+ controller.largeScreenActive = false // Make sure there's a change
+ clearInvocations(view)
+
+ controller.largeScreenActive = true
+
+ verify(view).setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
+ }
+
+ @Test
+ fun testLargeScreenActive_false() {
+ controller.largeScreenActive = true // Make sure there's a change
+ clearInvocations(view)
+
+ controller.largeScreenActive = false
+
+ verify(view).setTransition(HEADER_TRANSITION_ID)
+ }
+
+ @Test
+ fun testShadeExpandedFraction() {
+ // View needs to be visible for this to actually take effect
+ controller.qsVisible = true
+
+ clearInvocations(view)
+ controller.shadeExpandedFraction = 0.3f
+ verify(view).alpha = ShadeInterpolation.getContentAlpha(0.3f)
+
+ clearInvocations(view)
+ controller.shadeExpandedFraction = 1f
+ verify(view).alpha = ShadeInterpolation.getContentAlpha(1f)
+
+ clearInvocations(view)
+ controller.shadeExpandedFraction = 0f
+ verify(view).alpha = ShadeInterpolation.getContentAlpha(0f)
+ }
+
+ @Test
+ fun testQsExpandedFraction_headerTransition() {
+ controller.qsVisible = true
+ controller.largeScreenActive = false
+
+ clearInvocations(view)
+ controller.qsExpandedFraction = 0.3f
+ verify(view).progress = 0.3f
+ }
+
+ @Test
+ fun testQsExpandedFraction_largeScreen() {
+ controller.qsVisible = true
+ controller.largeScreenActive = true
+
+ clearInvocations(view)
+ controller.qsExpandedFraction = 0.3f
+ verify(view, never()).progress = anyFloat()
+ }
+
+ @Test
+ fun testScrollY_headerTransition() {
+ controller.largeScreenActive = false
+
+ clearInvocations(view)
+ controller.qsScrollY = 20
+ verify(view).scrollY = 20
+ }
+
+ @Test
+ fun testScrollY_largeScreen() {
+ controller.largeScreenActive = true
+
+ clearInvocations(view)
+ controller.qsScrollY = 20
+ verify(view, never()).scrollY = anyInt()
+ }
+
+ @Test
+ fun testPrivacyChipVisibilityChanged_visible_changesCorrectConstraints() {
+ val chipVisibleChanges = createMockConstraintChanges()
+ val chipNotVisibleChanges = createMockConstraintChanges()
+
+ whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(true))
+ .thenReturn(chipVisibleChanges)
+ whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(false))
+ .thenReturn(chipNotVisibleChanges)
+
+ val captor = ArgumentCaptor.forClass(ChipVisibilityListener::class.java)
+ verify(privacyIconsController).chipVisibilityListener = capture(captor)
+
+ captor.value.onChipVisibilityRefreshed(true)
+
+ verify(chipVisibleChanges.qqsConstraintsChanges)!!.invoke(qqsConstraints)
+ verify(chipVisibleChanges.qsConstraintsChanges)!!.invoke(qsConstraints)
+ verify(chipVisibleChanges.largeScreenConstraintsChanges)!!.invoke(largeScreenConstraints)
+
+ verify(chipNotVisibleChanges.qqsConstraintsChanges, never())!!.invoke(any())
+ verify(chipNotVisibleChanges.qsConstraintsChanges, never())!!.invoke(any())
+ verify(chipNotVisibleChanges.largeScreenConstraintsChanges, never())!!.invoke(any())
+ }
+
+ @Test
+ fun testPrivacyChipVisibilityChanged_notVisible_changesCorrectConstraints() {
+ val chipVisibleChanges = createMockConstraintChanges()
+ val chipNotVisibleChanges = createMockConstraintChanges()
+
+ whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(true))
+ .thenReturn(chipVisibleChanges)
+ whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(false))
+ .thenReturn(chipNotVisibleChanges)
+
+ val captor = ArgumentCaptor.forClass(ChipVisibilityListener::class.java)
+ verify(privacyIconsController).chipVisibilityListener = capture(captor)
+
+ captor.value.onChipVisibilityRefreshed(false)
+
+ verify(chipVisibleChanges.qqsConstraintsChanges, never())!!.invoke(qqsConstraints)
+ verify(chipVisibleChanges.qsConstraintsChanges, never())!!.invoke(qsConstraints)
+ verify(chipVisibleChanges.largeScreenConstraintsChanges, never())!!
+ .invoke(largeScreenConstraints)
+
+ verify(chipNotVisibleChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(chipNotVisibleChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(chipNotVisibleChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testInsetsGuides_ltr() {
+ whenever(view.isLayoutRtl).thenReturn(false)
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ val (insetLeft, insetRight) = 30 to 40
+ val (paddingStart, paddingEnd) = 10 to 20
+ whenever(view.paddingStart).thenReturn(paddingStart)
+ whenever(view.paddingEnd).thenReturn(paddingEnd)
+
+ mockInsetsProvider(insetLeft to insetRight, false)
+
+ whenever(combinedShadeHeadersConstraintManager
+ .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
+ ).thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+ verify(combinedShadeHeadersConstraintManager)
+ .edgesGuidelinesConstraints(insetLeft, paddingStart, insetRight, paddingEnd)
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testInsetsGuides_rtl() {
+ whenever(view.isLayoutRtl).thenReturn(true)
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ val (insetLeft, insetRight) = 30 to 40
+ val (paddingStart, paddingEnd) = 10 to 20
+ whenever(view.paddingStart).thenReturn(paddingStart)
+ whenever(view.paddingEnd).thenReturn(paddingEnd)
+
+ mockInsetsProvider(insetLeft to insetRight, false)
+
+ whenever(combinedShadeHeadersConstraintManager
+ .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
+ ).thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+ verify(combinedShadeHeadersConstraintManager)
+ .edgesGuidelinesConstraints(insetRight, paddingStart, insetLeft, paddingEnd)
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testNullCutout() {
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+ .thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets(null))
+
+ verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+ verify(combinedShadeHeadersConstraintManager, never())
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testEmptyCutout() {
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+ .thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+ verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+ verify(combinedShadeHeadersConstraintManager, never())
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testCornerCutout_emptyRect() {
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ mockInsetsProvider(0 to 0, true)
+
+ whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+ .thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+ verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+ verify(combinedShadeHeadersConstraintManager, never())
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testCornerCutout_nonEmptyRect() {
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ mockInsetsProvider(0 to 0, true)
+
+ whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+ .thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(1, 2, 3, 4)))
+
+ verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+ verify(combinedShadeHeadersConstraintManager, never())
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testTopCutout_ltr() {
+ val width = 100
+ val paddingLeft = 10
+ val paddingRight = 20
+ val cutoutWidth = 30
+
+ whenever(view.isLayoutRtl).thenReturn(false)
+ whenever(view.width).thenReturn(width)
+ whenever(view.paddingLeft).thenReturn(paddingLeft)
+ whenever(view.paddingRight).thenReturn(paddingRight)
+
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ mockInsetsProvider(0 to 0, false)
+
+ whenever(combinedShadeHeadersConstraintManager
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+ ).thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1)))
+
+ verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints()
+ val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2
+ verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(false, offset)
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testTopCutout_rtl() {
+ val width = 100
+ val paddingLeft = 10
+ val paddingRight = 20
+ val cutoutWidth = 30
+
+ whenever(view.isLayoutRtl).thenReturn(true)
+ whenever(view.width).thenReturn(width)
+ whenever(view.paddingLeft).thenReturn(paddingLeft)
+ whenever(view.paddingRight).thenReturn(paddingRight)
+
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ mockInsetsProvider(0 to 0, false)
+
+ whenever(combinedShadeHeadersConstraintManager
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+ ).thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1)))
+
+ verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints()
+ val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2
+ verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(true, offset)
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ private fun createWindowInsets(
+ topCutout: Rect? = Rect()
+ ): WindowInsets {
+ val windowInsets: WindowInsets = mock()
+ val displayCutout: DisplayCutout = mock()
+ whenever(windowInsets.displayCutout)
+ .thenReturn(if (topCutout != null) displayCutout else null)
+ whenever(displayCutout.boundingRectTop).thenReturn(topCutout)
+
+ return windowInsets
+ }
+
+ private fun mockInsetsProvider(
+ insets: Pair<Int, Int> = 0 to 0,
+ cornerCutout: Boolean = false,
+ ) {
+ whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
+ .thenReturn(insets.toAndroidPair())
+ whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(cornerCutout)
+ }
+
+ private fun createMockConstraintChanges(): ConstraintsChanges {
+ return ConstraintsChanges(mock(), mock(), mock())
+ }
+
+ private fun XmlResourceParser.getResId(): Int {
+ return Resources.getAttributeSetSourceResId(this)
+ }
+
+ private fun setUpMotionLayout(motionLayout: MotionLayout) {
+ whenever(motionLayout.getConstraintSet(QQS_HEADER_CONSTRAINT)).thenReturn(qqsConstraints)
+ whenever(motionLayout.getConstraintSet(QS_HEADER_CONSTRAINT)).thenReturn(qsConstraints)
+ whenever(motionLayout.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT))
+ .thenReturn(largeScreenConstraints)
+ }
+
+ private fun setUpDefaultInsets() {
+ whenever(combinedShadeHeadersConstraintManager
+ .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
+ ).thenReturn(EMPTY_CHANGES)
+ whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+ .thenReturn(EMPTY_CHANGES)
+ whenever(combinedShadeHeadersConstraintManager
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+ ).thenReturn(EMPTY_CHANGES)
+ whenever(combinedShadeHeadersConstraintManager
+ .privacyChipVisibilityConstraints(anyBoolean())
+ ).thenReturn(EMPTY_CHANGES)
+ whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
+ .thenReturn(Pair(0, 0).toAndroidPair())
+ whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(false)
+ }
+
+ private fun<T, U> Pair<T, U>.toAndroidPair(): android.util.Pair<T, U> {
+ return android.util.Pair(first, second)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
similarity index 75%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 8066401..02b26db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -1,10 +1,8 @@
-package com.android.systemui.statusbar.phone
+package com.android.systemui.shade
import android.app.StatusBarManager
import android.content.Context
-import android.content.res.TypedArray
import android.testing.AndroidTestingRunner
-import android.util.TypedValue.COMPLEX_UNIT_PX
import android.view.View
import android.widget.TextView
import androidx.test.filters.SmallTest
@@ -19,19 +17,24 @@
import com.android.systemui.qs.HeaderPrivacyIconsController
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.VariableDateViewController
+import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
+import org.junit.After
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.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -49,10 +52,14 @@
@Mock private lateinit var batteryMeterView: BatteryMeterView
@Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
@Mock private lateinit var privacyIconsController: HeaderPrivacyIconsController
+ @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
+ @Mock private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
+ @Mock private lateinit var variableDateViewController: VariableDateViewController
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var combinedShadeHeadersConstraintManager:
+ CombinedShadeHeadersConstraintManager
@Mock private lateinit var mockedContext: Context
- @Mock private lateinit var typedArray: TypedArray
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
var viewVisibility = View.GONE
@@ -65,7 +72,6 @@
fun setup() {
whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
whenever(clock.context).thenReturn(mockedContext)
- whenever(mockedContext.obtainStyledAttributes(anyInt(), any())).thenReturn(typedArray)
whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
whenever(date.context).thenReturn(mockedContext)
whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
@@ -73,6 +79,7 @@
.thenReturn(batteryMeterView)
whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
whenever(view.context).thenReturn(context)
+ whenever(view.resources).thenReturn(context.resources)
whenever(statusIcons.context).thenReturn(context)
whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
.thenReturn(qsCarrierGroupControllerBuilder)
@@ -82,27 +89,39 @@
null
}
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+ whenever(variableDateViewControllerFactory.create(any()))
+ .thenReturn(variableDateViewController)
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
view,
statusBarIconController,
privacyIconsController,
+ insetsProvider,
configurationController,
- qsCarrierGroupControllerBuilder,
- featureFlags,
+ variableDateViewControllerFactory,
batteryMeterViewController,
- dumpManager
+ dumpManager,
+ featureFlags,
+ qsCarrierGroupControllerBuilder,
+ combinedShadeHeadersConstraintManager
)
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ mLargeScreenShadeHeaderController.init()
carrierIconSlots = listOf(
context.getString(com.android.internal.R.string.status_bar_mobile))
}
+ @After
+ fun verifyEveryTest() {
+ verifyZeroInteractions(combinedShadeHeadersConstraintManager)
+ }
+
@Test
fun setVisible_onlyWhenActive() {
makeShadeVisible()
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
- mLargeScreenShadeHeaderController.active = false
+ mLargeScreenShadeHeaderController.largeScreenActive = false
assertThat(viewVisibility).isEqualTo(View.GONE)
}
@@ -156,41 +175,16 @@
}
private fun makeShadeVisible() {
- mLargeScreenShadeHeaderController.active = true
- mLargeScreenShadeHeaderController.shadeExpanded = true
+ mLargeScreenShadeHeaderController.largeScreenActive = true
+ mLargeScreenShadeHeaderController.qsVisible = true
}
@Test
- fun updateConfig_changesFontSize() {
- val updatedTextPixelSize = 32
- setReturnTextSize(updatedTextPixelSize)
-
+ fun updateConfig_changesFontStyle() {
configurationController.notifyDensityOrFontScaleChanged()
- verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat())
- verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat())
- verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
- }
-
- @Test
- fun updateConfig_changesFontSizeMultipleTimes() {
- val updatedTextPixelSize1 = 32
- setReturnTextSize(updatedTextPixelSize1)
- configurationController.notifyDensityOrFontScaleChanged()
- verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat())
- verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat())
- verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
- clearInvocations(carrierGroup)
-
- val updatedTextPixelSize2 = 42
- setReturnTextSize(updatedTextPixelSize2)
- configurationController.notifyDensityOrFontScaleChanged()
- verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat())
- verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat())
- verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
- }
-
- private fun setReturnTextSize(resultTextSize: Int) {
- whenever(typedArray.getDimensionPixelSize(anyInt(), anyInt())).thenReturn(resultTextSize)
+ verify(clock).setTextAppearance(R.style.TextAppearance_QS_Status)
+ verify(date).setTextAppearance(R.style.TextAppearance_QS_Status)
+ verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 5abcff3..e2673bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -143,10 +143,8 @@
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
-import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.PanelViewController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -1182,11 +1180,11 @@
mStatusBarStateController.setState(SHADE);
when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true);
mNotificationPanelViewController.updateResources();
- verify(mLargeScreenShadeHeaderController).setActive(true);
+ verify(mLargeScreenShadeHeaderController).setLargeScreenActive(true);
when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false);
mNotificationPanelViewController.updateResources();
- verify(mLargeScreenShadeHeaderController).setActive(false);
+ verify(mLargeScreenShadeHeaderController).setLargeScreenActive(false);
}
@Test
@@ -1286,6 +1284,29 @@
}
@Test
+ public void testPanelClosedWhenClosingQsInSplitShade() {
+ mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
+ /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+ enableSplitShade(/* enabled= */ true);
+ mNotificationPanelViewController.setExpandedFraction(1f);
+
+ assertThat(mNotificationPanelViewController.isClosing()).isFalse();
+ mNotificationPanelViewController.animateCloseQs(false);
+ assertThat(mNotificationPanelViewController.isClosing()).isTrue();
+ }
+
+ @Test
+ public void testPanelStaysOpenWhenClosingQs() {
+ mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
+ /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+ mNotificationPanelViewController.setExpandedFraction(1f);
+
+ assertThat(mNotificationPanelViewController.isClosing()).isFalse();
+ mNotificationPanelViewController.animateCloseQs(false);
+ assertThat(mNotificationPanelViewController.isClosing()).isFalse();
+ }
+
+ @Test
public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() {
mNotificationPanelViewController.mQs = mQs;
when(mQsFrame.getX()).thenReturn(0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 72d3c2e..5386171 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -27,6 +27,7 @@
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
import static com.google.common.truth.Truth.assertThat;
@@ -61,6 +62,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -87,6 +89,8 @@
@Mock
StatusBarStateController mStatusBarStateController;
@Mock
+ KeyguardStateController mKeyguardStateController;
+ @Mock
HeadsUpManager mHeadsUpManager;
@Mock
NotificationInterruptLogger mLogger;
@@ -106,6 +110,7 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
mNotifInterruptionStateProvider =
new NotificationInterruptStateProviderImpl(
@@ -116,6 +121,7 @@
mNotificationFilter,
mBatteryController,
mStatusBarStateController,
+ mKeyguardStateController,
mHeadsUpManager,
mLogger,
mMockHandler,
@@ -427,6 +433,12 @@
}
@Test
+ public void testShouldNotFullScreen_notPendingIntent_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldNotFullScreen_notPendingIntent();
+ }
+
+ @Test
public void testShouldNotFullScreen_notPendingIntent() throws RemoteException {
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -441,6 +453,12 @@
}
@Test
+ public void testShouldNotFullScreen_notHighImportance_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldNotFullScreen_notHighImportance();
+ }
+
+ @Test
public void testShouldNotFullScreen_notHighImportance() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -455,6 +473,12 @@
}
@Test
+ public void testShouldNotFullScreen_isGroupAlertSilenced_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldNotFullScreen_isGroupAlertSilenced();
+ }
+
+ @Test
public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true);
when(mPowerManager.isInteractive()).thenReturn(false);
@@ -469,6 +493,12 @@
}
@Test
+ public void testShouldFullScreen_notInteractive_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldFullScreen_notInteractive();
+ }
+
+ @Test
public void testShouldFullScreen_notInteractive() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(false);
@@ -483,6 +513,12 @@
}
@Test
+ public void testShouldFullScreen_isDreaming_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldFullScreen_isDreaming();
+ }
+
+ @Test
public void testShouldFullScreen_isDreaming() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -497,6 +533,12 @@
}
@Test
+ public void testShouldFullScreen_onKeyguard_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldFullScreen_onKeyguard();
+ }
+
+ @Test
public void testShouldFullScreen_onKeyguard() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -511,6 +553,12 @@
}
@Test
+ public void testShouldNotFullScreen_willHun_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldNotFullScreen_willHun();
+ }
+
+ @Test
public void testShouldNotFullScreen_willHun() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
when(mPowerManager.isInteractive()).thenReturn(true);
@@ -542,6 +590,66 @@
verify(mLogger).logFullscreen(entry, "Expected not to HUN");
}
+ @Test
+ public void testShouldFullScreen_snoozed_occluding_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Expected not to HUN while keyguard occluded");
+ }
+
+ @Test
+ public void testShouldFullScreen_snoozed_lockedShade_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Keyguard is showing and not occluded");
+ }
+
+ @Test
+ public void testShouldNotFullScreen_snoozed_unlocked_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
/**
* Bubbles can happen.
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
index 64d0256..214ba16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt
@@ -98,7 +98,7 @@
@Test
fun testPrepareDialogForApp_onlyDefaultChannel() {
- group.addChannel(channelDefault)
+ group.channels = listOf(channelDefault)
controller.prepareDialogForApp(TEST_APP_NAME, TEST_PACKAGE_NAME, TEST_UID,
setOf(channelDefault), appIcon, clickListener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index b75c52a..c6fb0ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -306,8 +306,13 @@
mNotificationInterruptStateProvider =
new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
mPowerManager,
- mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
- mStatusBarStateController, mBatteryController, mHeadsUpManager,
+ mDreamManager,
+ mAmbientDisplayConfiguration,
+ mNotificationFilter,
+ mStatusBarStateController,
+ mKeyguardStateController,
+ mBatteryController,
+ mHeadsUpManager,
mock(NotificationInterruptLogger.class),
new Handler(TestableLooper.get(this).getLooper()),
mock(NotifPipelineFlags.class),
@@ -1036,15 +1041,28 @@
AmbientDisplayConfiguration ambientDisplayConfiguration,
NotificationFilter filter,
StatusBarStateController controller,
+ KeyguardStateController keyguardStateController,
BatteryController batteryController,
HeadsUpManager headsUpManager,
NotificationInterruptLogger logger,
Handler mainHandler,
NotifPipelineFlags flags,
KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
- super(contentResolver, powerManager, dreamManager, ambientDisplayConfiguration, filter,
- batteryController, controller, headsUpManager, logger, mainHandler,
- flags, keyguardNotificationVisibilityProvider);
+ super(
+ contentResolver,
+ powerManager,
+ dreamManager,
+ ambientDisplayConfiguration,
+ filter,
+ batteryController,
+ controller,
+ keyguardStateController,
+ headsUpManager,
+ logger,
+ mainHandler,
+ flags,
+ keyguardNotificationVisibilityProvider
+ );
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index ba29e95..9892448 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -26,6 +26,7 @@
import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.PanelViewController
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
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 8d686ae..d6c995b 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
@@ -20,6 +20,7 @@
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.PanelViewController
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
new file mode 100644
index 0000000..d84010d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider.BoundsChangeListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class StatusBarBoundsProviderTest : SysuiTestCase() {
+
+ companion object {
+ private val START_SIDE_BOUNDS = Rect(50, 100, 150, 200)
+ private val END_SIDE_BOUNDS = Rect(250, 300, 350, 400)
+ }
+
+ @Mock private lateinit var boundsChangeListener: BoundsChangeListener
+
+ private lateinit var boundsProvider: StatusBarBoundsProvider
+
+ private lateinit var startSideContent: View
+ private lateinit var endSideContent: View
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ startSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(START_SIDE_BOUNDS) }
+ endSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(END_SIDE_BOUNDS) }
+
+ boundsProvider =
+ StatusBarBoundsProvider(setOf(boundsChangeListener), startSideContent, endSideContent)
+ }
+
+ @Test
+ fun visibleStartSideBounds_returnsBoundsFromStartSideContentView() {
+ assertThat(boundsProvider.visibleStartSideBounds).isEqualTo(START_SIDE_BOUNDS)
+ }
+
+ @Test
+ fun visibleEndSideBounds_returnsBoundsFromEndSideContentView() {
+ assertThat(boundsProvider.visibleEndSideBounds).isEqualTo(END_SIDE_BOUNDS)
+ }
+
+ @Test
+ fun startBoundsChange_afterStart_notifiesListener() {
+ boundsProvider.start()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+ startSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun startBoundsChange_beforeStart_doesNotNotifyListener() {
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+ startSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun startBoundsChange_afterStop_doesNotNotifyListener() {
+ boundsProvider.start()
+ boundsProvider.stop()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+ startSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun startLayoutChange_afterStart_boundsOnScreenSame_doesNotNotifyListener() {
+ boundsProvider.start()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+ startSideContent.layout(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun endBoundsChange_afterStart_notifiesListener() {
+ boundsProvider.start()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+ endSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun endBoundsChange_beforeStart_doesNotNotifyListener() {
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+ endSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun endBoundsChange_afterStop_doesNotNotifyListener() {
+ boundsProvider.start()
+ boundsProvider.stop()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+ endSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun endLayoutChange_afterStart_boundsOnScreenSame_doesNotNotifyListener() {
+ boundsProvider.start()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+ endSideContent.layout(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+}
+
+private fun View.setBoundsOnScreen(bounds: Rect) {
+ doAnswer { invocation ->
+ val boundsOutput = invocation.arguments[0] as Rect
+ boundsOutput.set(bounds)
+ return@doAnswer Unit
+ }
+ .`when`(this)
+ .getBoundsOnScreen(any())
+ layout(bounds.left, bounds.top, bounds.right, bounds.bottom)
+}
+
+private fun View.layout(rect: Rect) {
+ layout(rect.left, rect.top, rect.right, rect.bottom)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 4b5d1f7..fdb2977 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -16,12 +16,14 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
+import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.metrics.LogMaker;
import android.support.test.metricshelper.MetricsAsserts;
@@ -80,6 +82,8 @@
private FakeMetricsLogger mMetricsLogger;
private ShadeController mShadeController = mock(ShadeController.class);
private CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
+ private KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class);
+ private NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
private InitController mInitController = new InitController();
@Before
@@ -114,7 +118,7 @@
mock(ScrimController.class),
mock(NotificationShadeWindowController.class),
mock(DynamicPrivacyController.class),
- mock(KeyguardStateController.class),
+ mKeyguardStateController,
mock(KeyguardIndicationController.class),
mCentralSurfaces,
mock(ShadeControllerImpl.class),
@@ -130,7 +134,7 @@
mInitController,
mNotificationInterruptStateProvider,
mock(NotificationRemoteInputManager.class),
- mock(NotifPipelineFlags.class),
+ mNotifPipelineFlags,
mock(NotificationRemoteInputManager.Callback.class),
mock(NotificationListContainer.class));
mInitController.executePostInitTasks();
@@ -141,6 +145,19 @@
}
@Test
+ public void testNoSuppressHeadsUp_default() {
+ Notification n = new Notification.Builder(getContext(), "a").build();
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg("a")
+ .setOpPkg("a")
+ .setTag("a")
+ .setNotification(n)
+ .build();
+
+ assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+ }
+
+ @Test
public void testSuppressHeadsUp_disabledStatusBar() {
Notification n = new Notification.Builder(getContext(), "a").build();
NotificationEntry entry = new NotificationEntryBuilder()
@@ -176,6 +193,63 @@
}
@Test
+ public void testNoSuppressHeadsUp_FSI_occludedKeygaurd() {
+ when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setFullScreenIntent(mock(PendingIntent.class), true)
+ .build();
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg("a")
+ .setOpPkg("a")
+ .setTag("a")
+ .setNotification(n)
+ .build();
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ when(mCentralSurfaces.isOccluded()).thenReturn(true);
+ assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+ }
+
+ @Test
+ public void testSuppressHeadsUp_FSI_nonOccludedKeygaurd() {
+ when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setFullScreenIntent(mock(PendingIntent.class), true)
+ .build();
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg("a")
+ .setOpPkg("a")
+ .setTag("a")
+ .setNotification(n)
+ .build();
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ when(mCentralSurfaces.isOccluded()).thenReturn(false);
+ assertTrue(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+ }
+
+ @Test
+ public void testNoSuppressHeadsUp_FSI_nonOccludedKeygaurd_withNewFlag() {
+ when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setFullScreenIntent(mock(PendingIntent.class), true)
+ .build();
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg("a")
+ .setOpPkg("a")
+ .setTag("a")
+ .setNotification(n)
+ .build();
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ when(mCentralSurfaces.isOccluded()).thenReturn(false);
+ assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+ }
+
+ @Test
public void testSuppressInterruptions_vrMode() {
Notification n = new Notification.Builder(getContext(), "a").build();
NotificationEntry entry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 681e998..56804d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -41,6 +41,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogcatEchoTracker;
@@ -107,6 +108,8 @@
private NotificationPanelViewController mNotificationPanelViewController;
@Mock
private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
+ @Mock
+ private DumpManager mDumpManager;
public CollapsedStatusBarFragmentTest() {
super(CollapsedStatusBarFragment.class);
@@ -386,7 +389,8 @@
),
mOperatorNameViewControllerFactory,
mSecureSettings,
- mExecutor);
+ mExecutor,
+ mDumpManager);
}
private void setUpDaggerComponent() {
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 6a0124a..9866013 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -321,6 +321,7 @@
mock(AmbientDisplayConfiguration.class),
mock(NotificationFilter.class),
mock(StatusBarStateController.class),
+ mock(KeyguardStateController.class),
mock(BatteryController.class),
mock(HeadsUpManager.class),
mock(NotificationInterruptLogger.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index a7f0dc2..d80ea15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
public class TestableNotificationInterruptStateProviderImpl
extends NotificationInterruptStateProviderImpl {
@@ -41,6 +42,7 @@
AmbientDisplayConfiguration ambientDisplayConfiguration,
NotificationFilter filter,
StatusBarStateController statusBarStateController,
+ KeyguardStateController keyguardStateController,
BatteryController batteryController,
HeadsUpManager headsUpManager,
NotificationInterruptLogger logger,
@@ -54,6 +56,7 @@
filter,
batteryController,
statusBarStateController,
+ keyguardStateController,
headsUpManager,
logger,
mainHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 72ade26..9c21366 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -33,7 +33,6 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer;
-import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -73,7 +72,6 @@
@Mock OneHanded mOneHanded;
@Mock WakefulnessLifecycle mWakefulnessLifecycle;
@Mock ProtoTracer mProtoTracer;
- @Mock ShellCommandHandler mShellCommandHandler;
@Mock UserInfoController mUserInfoController;
@Mock ShellExecutor mSysUiMainExecutor;
@@ -82,10 +80,10 @@
MockitoAnnotations.initMocks(this);
mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
- Optional.of(mSplitScreen), Optional.of(mOneHanded),
- Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
- mKeyguardStateController, mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState,
- mProtoTracer, mWakefulnessLifecycle, mUserInfoController, mSysUiMainExecutor);
+ Optional.of(mSplitScreen), Optional.of(mOneHanded), mCommandQueue,
+ mConfigurationController, mKeyguardStateController, mKeyguardUpdateMonitor,
+ mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle,
+ mUserInfoController, mSysUiMainExecutor);
}
@Test
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0040ea9..3a869f8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9181,11 +9181,8 @@
Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
if (settings == null) {
Log.e(TAG, "error reading spatial audio device settings");
- } else {
- Log.v(TAG, "restoring spatial audio device settings: " + settings);
- mSpatializerHelper.setSADeviceSettings(settings);
}
- mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
+ mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect, settings);
mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
}
@@ -10373,6 +10370,11 @@
@Override
public void setAccessibilityServiceUids(IntArray uids) {
+ // TODO(b/233287010): Fix voice interaction and a11y concurrency in audio policy service
+ if (isPlatformAutomotive()) {
+ return;
+ }
+
synchronized (mAccessibilityServiceUidsLock) {
if (uids.size() == 0) {
mAccessibilityServiceUids = null;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index dd44af1..cd5960f 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -175,7 +175,7 @@
mASA = asa;
}
- synchronized void init(boolean effectExpected) {
+ synchronized void init(boolean effectExpected, @Nullable String settings) {
loglogi("init effectExpected=" + effectExpected);
if (!effectExpected) {
loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected");
@@ -278,6 +278,13 @@
mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i));
}
}
+
+ // When initialized from AudioService, the settings string will be non-null.
+ // Saved settings need to be applied after spatialization support is initialized above.
+ if (settings != null) {
+ setSADeviceSettings(settings);
+ }
+
// for both transaural / binaural, we are not forcing enablement as the init() method
// could have been called another time after boot in case of audioserver restart
addCompatibleAudioDevice(
@@ -316,7 +323,7 @@
mState = STATE_UNINITIALIZED;
mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
- init(true);
+ init(true, null /* settings */);
setSpatializerEnabledInt(featureEnabled);
}
@@ -731,7 +738,7 @@
return;
}
if (mState == STATE_UNINITIALIZED) {
- init(true);
+ init(true, null /* settings */);
}
setSpatializerEnabledInt(true);
} else {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java b/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java
index d99abcd..9494547 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java
@@ -70,20 +70,24 @@
private int mAcceptCount;
private int mRejectCount;
- private SparseIntArray mErrorCount;
+ private int mErrorCount;
+ private int mAuthAttemptCount;
+ private SparseIntArray mErrorFrequencyMap;
private long mAcceptLatency;
private long mRejectLatency;
- private SparseLongArray mErrorLatency;
+ private long mErrorLatency;
+ private SparseLongArray mErrorLatencyMap;
public UsageStats(Context context) {
mAuthenticationEvents = new ArrayDeque<>();
- mErrorCount = new SparseIntArray();
- mErrorLatency = new SparseLongArray();
+ mErrorFrequencyMap = new SparseIntArray();
+ mErrorLatencyMap = new SparseLongArray();
mContext = context;
}
public void addEvent(AuthenticationEvent event) {
+ mAuthAttemptCount++;
if (mAuthenticationEvents.size() >= EVENT_LOG_SIZE) {
mAuthenticationEvents.removeFirst();
}
@@ -96,29 +100,38 @@
mRejectCount++;
mRejectLatency += event.mLatency;
} else {
- mErrorCount.put(event.mError, mErrorCount.get(event.mError, 0) + 1);
- mErrorLatency.put(event.mError, mErrorLatency.get(event.mError, 0L) + event.mLatency);
+ mErrorCount++;
+ mErrorLatency += event.mLatency;
+ mErrorFrequencyMap.put(event.mError, mErrorFrequencyMap.get(event.mError, 0) + 1);
+ mErrorLatencyMap.put(event.mError,
+ mErrorLatencyMap.get(event.mError, 0L) + event.mLatency);
}
}
public void print(PrintWriter pw) {
- pw.println("Events since last reboot: " + mAuthenticationEvents.size());
+ pw.println("Printing most recent events since last reboot("
+ + mAuthenticationEvents.size() + " events)");
for (AuthenticationEvent event : mAuthenticationEvents) {
pw.println(event.toString(mContext));
}
// Dump aggregated usage stats
- pw.println("Accept\tCount: " + mAcceptCount + "\tLatency: " + mAcceptLatency
+ pw.println("");
+ pw.println("Accept Count: " + mAcceptCount + "\tLatency: " + mAcceptLatency
+ "\tAverage: " + (mAcceptCount > 0 ? mAcceptLatency / mAcceptCount : 0));
- pw.println("Reject\tCount: " + mRejectCount + "\tLatency: " + mRejectLatency
+ pw.println("Reject Count: " + mRejectCount + "\tLatency: " + mRejectLatency
+ "\tAverage: " + (mRejectCount > 0 ? mRejectLatency / mRejectCount : 0));
+ pw.println("Total Error Count: " + mErrorCount + "\tLatency: " + mErrorLatency
+ + "\tAverage: " + (mErrorCount > 0 ? mErrorLatency / mErrorCount : 0));
+ pw.println("Total Attempts: " + mAuthAttemptCount);
+ pw.println("");
- for (int i = 0; i < mErrorCount.size(); i++) {
- final int key = mErrorCount.keyAt(i);
- final int count = mErrorCount.get(i);
+ for (int i = 0; i < mErrorFrequencyMap.size(); i++) {
+ final int key = mErrorFrequencyMap.keyAt(i);
+ final int count = mErrorFrequencyMap.get(key);
pw.println("Error" + key + "\tCount: " + count
- + "\tLatency: " + mErrorLatency.get(key, 0L)
- + "\tAverage: " + (count > 0 ? mErrorLatency.get(key, 0L) / count : 0)
+ + "\tLatency: " + mErrorLatencyMap.get(key, 0L)
+ + "\tAverage: " + (count > 0 ? mErrorLatencyMap.get(key, 0L) / count : 0)
+ "\t" + FaceManager.getErrorString(mContext, key, 0 /* vendorCode */));
}
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 2322280d..b5aa7b1 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -287,7 +287,8 @@
private BrightnessThrottlingData mBrightnessThrottlingData;
private BrightnessThrottlingData mOriginalBrightnessThrottlingData;
- private DisplayDeviceConfig(Context context) {
+ @VisibleForTesting
+ DisplayDeviceConfig(Context context) {
mContext = context;
}
@@ -691,7 +692,8 @@
return config;
}
- private boolean initFromFile(File configFile) {
+ @VisibleForTesting
+ boolean initFromFile(File configFile) {
if (!configFile.exists()) {
// Display configuration files aren't required to exist.
return false;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9486a45..e589080 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2459,11 +2459,11 @@
SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> {
try {
if (DBG) {
- Slog.d(TAG, "Reposting " + r.getKey());
+ Slog.d(TAG, "Reposting " + r.getKey() + " " + muteOnReturn);
}
enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(),
r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(),
- r.getSbn().getId(), r.getSbn().getNotification(), userId, true);
+ r.getSbn().getId(), r.getSbn().getNotification(), userId, muteOnReturn);
} catch (Exception e) {
Slog.e(TAG, "Cannot un-snooze notification", e);
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 477b8da..066692d 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -86,6 +86,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -1327,16 +1328,17 @@
return null;
}
NotificationChannelGroup group = r.groups.get(groupId).clone();
- group.setChannels(new ArrayList<>());
+ ArrayList channels = new ArrayList();
int N = r.channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel nc = r.channels.valueAt(i);
if (includeDeleted || !nc.isDeleted()) {
if (groupId.equals(nc.getGroup())) {
- group.addChannel(nc);
+ channels.add(nc);
}
}
}
+ group.setChannels(channels);
return group;
}
}
@@ -1357,44 +1359,48 @@
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Objects.requireNonNull(pkg);
- Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
+ List<NotificationChannelGroup> groups = new ArrayList<>();
synchronized (mPackagePreferences) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return ParceledListSlice.emptyList();
}
- NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
+ Map<String, ArrayList<NotificationChannel>> groupedChannels = new HashMap();
int N = r.channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel nc = r.channels.valueAt(i);
if (includeDeleted || !nc.isDeleted()) {
if (nc.getGroup() != null) {
if (r.groups.get(nc.getGroup()) != null) {
- NotificationChannelGroup ncg = groups.get(nc.getGroup());
- if (ncg == null) {
- ncg = r.groups.get(nc.getGroup()).clone();
- ncg.setChannels(new ArrayList<>());
- groups.put(nc.getGroup(), ncg);
-
- }
- ncg.addChannel(nc);
+ ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault(
+ nc.getGroup(), new ArrayList<>());
+ channels.add(nc);
+ groupedChannels.put(nc.getGroup(), channels);
}
} else {
- nonGrouped.addChannel(nc);
+ ArrayList<NotificationChannel> channels = groupedChannels.getOrDefault(
+ null, new ArrayList<>());
+ channels.add(nc);
+ groupedChannels.put(null, channels);
}
}
}
- if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
- groups.put(null, nonGrouped);
- }
- if (includeEmpty) {
- for (NotificationChannelGroup group : r.groups.values()) {
- if (!groups.containsKey(group.getId())) {
- groups.put(group.getId(), group);
- }
+ for (NotificationChannelGroup group : r.groups.values()) {
+ ArrayList<NotificationChannel> channels =
+ groupedChannels.getOrDefault(group.getId(), new ArrayList<>());
+ if (includeEmpty || !channels.isEmpty()) {
+ NotificationChannelGroup clone = group.clone();
+ clone.setChannels(channels);
+ groups.add(clone);
}
}
- return new ParceledListSlice<>(new ArrayList<>(groups.values()));
+
+ if (includeNonGrouped && groupedChannels.containsKey(null)) {
+ NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
+ nonGrouped.setChannels(groupedChannels.get(null));
+ groups.add(nonGrouped);
+ }
+ return new ParceledListSlice<>(groups);
}
}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0dc188b..46f0dbc 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -427,8 +427,7 @@
pkgSetting.setLastModifiedTime(scanFileTime);
// TODO(b/135203078): Remove, move to constructor
pkgSetting.setPkg(parsedPackage)
- .setFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting))
- .setPrivateFlags(
+ .setPkgFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting));
if (parsedPackage.getLongVersionCode() != pkgSetting.getVersionCode()) {
pkgSetting.setLongVersionCode(parsedPackage.getLongVersionCode());
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index b952f80..61a251e 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -146,6 +146,17 @@
return this;
}
+ /**
+ * Unconditionally set both mPkgFlags and mPkgPrivateFlags.
+ * Should not be used outside pkgSetting initialization or update.
+ */
+ SettingBase setPkgFlags(int flags, int privateFlags) {
+ this.mPkgFlags = flags;
+ this.mPkgPrivateFlags = privateFlags;
+ onChanged();
+ return this;
+ }
+
public int getFlags() {
return mPkgFlags;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6400502..7437b14 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -987,8 +987,7 @@
// Update new package state.
.setLastModifiedTime(codePath.lastModified())
.setDomainSetId(domainSetId);
- pkgSetting.setFlags(pkgFlags)
- .setPrivateFlags(pkgPrivateFlags);
+ pkgSetting.setPkgFlags(pkgFlags, pkgPrivateFlags);
} else {
pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
@@ -1175,15 +1174,15 @@
.setUsesStaticLibrariesVersions(null);
}
- // These two flags are preserved from the existing PackageSetting. Copied from prior code,
- // unclear if this is actually necessary.
- boolean wasExternalStorage = (pkgSetting.getFlags()
- & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
- if (wasExternalStorage) {
- pkgFlags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE;
- } else {
- pkgFlags &= ~ApplicationInfo.FLAG_EXTERNAL_STORAGE;
- }
+ // If what we are scanning is a system (and possibly privileged) package,
+ // then make it so, regardless of whether it was previously installed only
+ // in the data partition. Reset first.
+ int newPkgFlags = pkgSetting.getFlags();
+ newPkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
+ newPkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM;
+ // Only set pkgFlags.
+ pkgSetting.setPkgFlags(newPkgFlags, pkgSetting.getPrivateFlags());
+
boolean wasRequiredForSystemUser = (pkgSetting.getPrivateFlags()
& ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0;
if (wasRequiredForSystemUser) {
@@ -1191,9 +1190,7 @@
} else {
pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER;
}
-
- pkgSetting.setFlags(pkgFlags)
- .setPrivateFlags(pkgPrivateFlags);
+ pkgSetting.setPrivateFlags(pkgPrivateFlags);
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0a58044..a044e60 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7738,6 +7738,9 @@
// relatively fixed.
overrideConfig.colorMode = fullConfig.colorMode;
overrideConfig.densityDpi = fullConfig.densityDpi;
+ // The smallest screen width is the short side of screen bounds. Because the bounds
+ // and density won't be changed, smallestScreenWidthDp is also fixed.
+ overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
if (info.isFixedOrientation()) {
// lock rotation too. When in size-compat, onConfigurationChanged will watch for and
// apply runtime rotation changes.
@@ -7834,7 +7837,7 @@
// computed accordingly.
if (!matchParentBounds()) {
getTaskFragment().computeConfigResourceOverrides(resolvedConfig,
- newParentConfiguration, areBoundsLetterboxed());
+ newParentConfiguration);
}
// If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
// are already calculated in resolveFixedOrientationConfiguration.
@@ -8005,8 +8008,7 @@
}
// Since bounds has changed, the configuration needs to be computed accordingly.
- getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- areBoundsLetterboxed());
+ getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
}
void recomputeConfiguration() {
@@ -8222,7 +8224,7 @@
// Calculate app bounds using fixed orientation bounds because they will be needed later
// for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
- newParentConfig, mCompatDisplayInsets, areBoundsLetterboxed());
+ newParentConfig);
mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds);
}
@@ -8250,7 +8252,7 @@
// Compute the configuration based on the resolved bounds. If aspect ratio doesn't
// restrict, the bounds should be the requested override bounds.
getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- getFixedRotationTransformDisplayInfo(), areBoundsLetterboxed());
+ getFixedRotationTransformDisplayInfo());
}
}
@@ -8314,7 +8316,7 @@
// are calculated in compat container space. The actual position on screen will be applied
// later, so the calculation is simpler that doesn't need to involve offset from parent.
getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- mCompatDisplayInsets, areBoundsLetterboxed());
+ mCompatDisplayInsets);
// Use current screen layout as source because the size of app is independent to parent.
resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride(
getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d9c509c..90b77f7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -67,6 +67,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -5574,11 +5575,13 @@
private static boolean needsGestureExclusionRestrictions(WindowState win,
boolean ignoreRequest) {
final int type = win.mAttrs.type;
+ final int privateFlags = win.mAttrs.privateFlags;
final boolean stickyHideNav =
!win.getRequestedVisibility(ITYPE_NAVIGATION_BAR)
&& win.mAttrs.insetsFlags.behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
return (!stickyHideNav || ignoreRequest) && type != TYPE_INPUT_METHOD
- && type != TYPE_NOTIFICATION_SHADE && win.getActivityType() != ACTIVITY_TYPE_HOME;
+ && type != TYPE_NOTIFICATION_SHADE && win.getActivityType() != ACTIVITY_TYPE_HOME
+ && (privateFlags & PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION) == 0;
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 98a51a9..4a7a8bd 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -47,6 +47,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -974,6 +975,10 @@
}
}
+ if (!win.mSession.mCanSetUnrestrictedGestureExclusion) {
+ attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
+ }
+
// Check if alternate bars positions were updated.
if (mStatusBarAlt == win) {
mStatusBarAltPosition = getAltBarPosition(attrs);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index b9d8319..3d91921 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -549,7 +549,7 @@
// Go through all tasks and collect them before the rotation
// TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper
// handling is synchronized.
- mDisplayContent.mTransitionController.collectForDisplayChange(mDisplayContent,
+ mDisplayContent.mTransitionController.collectForDisplayAreaChange(mDisplayContent,
null /* use collecting transition */);
}
mService.mAtmService.deferWindowLayout();
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index d209f08..64749cf 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -101,7 +101,7 @@
if (t != null) {
mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
- mTransitionController.collectForDisplayChange(mDisplayContent, t);
+ mTransitionController.collectForDisplayAreaChange(mDisplayContent, t);
mTransition = t;
}
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index f840171..ffe3374 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -520,7 +520,7 @@
}
private boolean matchesTarget(Task task) {
- return task.mUserId == mUserId
+ return task.getNonFinishingActivityCount() > 0 && task.mUserId == mUserId
&& task.getBaseIntent().getComponent().equals(mTargetIntent.getComponent());
}
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 9b013da..3577545 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
@@ -109,6 +110,7 @@
final boolean mCanCreateSystemApplicationOverlay;
final boolean mCanHideNonSystemOverlayWindows;
+ final boolean mCanSetUnrestrictedGestureExclusion;
private AlertWindowNotification mAlertWindowNotification;
private boolean mShowingAlertWindowNotificationAllowed;
private boolean mClientDead = false;
@@ -139,6 +141,9 @@
mSetsUnrestrictedKeepClearAreas =
service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_KEEP_CLEAR_AREAS)
== PERMISSION_GRANTED;
+ mCanSetUnrestrictedGestureExclusion =
+ service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_GESTURE_EXCLUSION)
+ == PERMISSION_GRANTED;
mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
mDragDropController = mService.mDragDropController;
StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 21c5886..f8a9d46 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1957,37 +1957,29 @@
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig) {
computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- null /* compatInsets */, false /* areBoundsLetterboxed */);
+ null /* compatInsets */);
}
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig, boolean areBoundsLetterboxed) {
- computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- null /* compatInsets */, areBoundsLetterboxed);
- }
-
- void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
- @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
- boolean areBoundsLetterboxed) {
+ @NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
if (overrideDisplayInfo != null) {
// Make sure the screen related configs can be computed by the provided display info.
inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
invalidateAppBoundsConfig(inOutConfig);
}
computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
- null /* compatInsets */, areBoundsLetterboxed);
+ null /* compatInsets */);
}
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig,
- @Nullable ActivityRecord.CompatDisplayInsets compatInsets,
- boolean areBoundsLetterboxed) {
+ @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
if (compatInsets != null) {
// Make sure the app bounds can be computed by the compat insets.
invalidateAppBoundsConfig(inOutConfig);
}
computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
- compatInsets, areBoundsLetterboxed);
+ compatInsets);
}
/**
@@ -2014,8 +2006,7 @@
**/
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo,
- @Nullable ActivityRecord.CompatDisplayInsets compatInsets,
- boolean areBoundsLetterboxed) {
+ @Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
int windowingMode = inOutConfig.windowConfiguration.getWindowingMode();
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = parentConfig.windowConfiguration.getWindowingMode();
@@ -2122,7 +2113,6 @@
: overrideScreenHeightDp;
}
- // TODO(b/238331848): Consider simplifying logic that computes smallestScreenWidthDp.
if (inOutConfig.smallestScreenWidthDp
== Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
// When entering to or exiting from Pip, the PipTaskOrganizer will set the
@@ -2138,10 +2128,9 @@
// task, because they should not be affected by insets.
inOutConfig.smallestScreenWidthDp = (int) (0.5f
+ Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
- } else if (isEmbedded() || areBoundsLetterboxed || customContainerPolicy) {
- // For embedded TFs and activities that are letteboxed or eligible for size
- // compat mode, the smallest width should be updated. Otherwise, inherit from
- // the parent task would result in applications loaded wrong resource.
+ } else if (isEmbedded()) {
+ // For embedded TFs, the smallest width should be updated. Otherwise, inherit
+ // from the parent task would result in applications loaded wrong resource.
inOutConfig.smallestScreenWidthDp =
Math.min(inOutConfig.screenWidthDp, inOutConfig.screenHeightDp);
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 88572a9..a02be25 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -460,23 +460,26 @@
* Collects the window containers which need to be synced with the changing display (e.g.
* rotating) to the given transition or the current collecting transition.
*/
- void collectForDisplayChange(@NonNull DisplayContent dc, @Nullable Transition incoming) {
+ void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc, @Nullable Transition incoming) {
if (incoming == null) incoming = mCollectingTransition;
if (incoming == null) return;
final Transition transition = incoming;
// Collect all visible tasks.
- dc.forAllLeafTasks(task -> {
+ wc.forAllLeafTasks(task -> {
if (task.isVisible()) {
transition.collect(task);
}
}, true /* traverseTopToBottom */);
// Collect all visible non-app windows which need to be drawn before the animation starts.
- dc.forAllWindows(w -> {
- if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
- && dc.shouldSyncRotationChange(w)) {
- transition.collect(w.mToken);
- }
- }, true /* traverseTopToBottom */);
+ final DisplayContent dc = wc.asDisplayContent();
+ if (dc != null) {
+ wc.forAllWindows(w -> {
+ if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
+ && dc.shouldSyncRotationChange(w)) {
+ transition.collect(w.mToken);
+ }
+ }, true /* traverseTopToBottom */);
+ }
}
/** @see Transition#mStatusBarTransitionDelay */
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 6bb5ece..ee64354 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -397,7 +397,7 @@
// Go through all tasks and collect them before the rotation
// TODO(shell-transitions): move collect() to onConfigurationChange once
// wallpaper handling is synchronized.
- dc.mTransitionController.collectForDisplayChange(dc, transition);
+ dc.mTransitionController.collectForDisplayAreaChange(dc, transition);
dc.sendNewConfiguration();
effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
@@ -421,6 +421,15 @@
addToSyncSet(syncId, wc);
}
if (transition != null) transition.collect(wc);
+ final DisplayArea da = wc.asDisplayArea();
+ // Only check DisplayArea here as a similar thing is done for DisplayContent above.
+ if (da != null && wc.asDisplayContent() == null
+ && entry.getValue().getWindowingMode() != da.getWindowingMode()) {
+ // Go through all tasks and collect them before changing the windowing mode of a
+ // display-level container.
+ // TODO(shell-transitions): handle this more elegantly.
+ da.mTransitionController.collectForDisplayAreaChange(da, transition);
+ }
if ((entry.getValue().getChangeMask()
& WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 67ef7f5..80de823 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -2357,10 +2357,15 @@
mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
alarmPi, null, null, null, alarmClock);
+ final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE),
- isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(),
- eq(EXACT_ALLOW_REASON_COMPAT));
+ isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE),
+ bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_COMPAT));
+
+ final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+ final int type = idleOptions.getTemporaryAppAllowlistType();
+ assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
new file mode 100644
index 0000000..7939303
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public final class DisplayDeviceConfigTest {
+ private DisplayDeviceConfig mDisplayDeviceConfig;
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private Resources mResources;
+
+ @Before
+ public void setUp() throws IOException {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
+ mockDeviceConfigs();
+ try {
+ Path tempFile = Files.createTempFile("display_config", ".tmp");
+ Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+ mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
+ mDisplayDeviceConfig.initFromFile(tempFile.toFile());
+ } catch (IOException e) {
+ throw new IOException("Failed to setup the display device config.", e);
+ }
+ }
+
+ @Test
+ public void testConfigValues() {
+ assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
+ assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
+ assertEquals(mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), 10.0f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), 2.0f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, 0.0f);
+ assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f},
+ 0.0f);
+ assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f}, 0.0f);
+ assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f},
+ 0.0f);
+ assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f);
+ assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
+
+ // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
+ // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
+ // Also add test for the case where optional display configs are null
+ }
+
+ private String getContent() {
+ return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<displayConfiguration>\n"
+ + "<screenBrightnessMap>\n"
+ + "<point>\n"
+ + "<value>0.0</value>\n"
+ + "<nits>2.0</nits>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<value>0.62</value>\n"
+ + "<nits>500.0</nits>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<value>1.0</value>\n"
+ + "<nits>800.0</nits>\n"
+ + "</point>\n"
+ + "</screenBrightnessMap>\n"
+ + "<highBrightnessMode enabled=\"true\">\n"
+ + "<transitionPoint>0.62</transitionPoint>\n"
+ + "<minimumLux>10000</minimumLux>\n"
+ + "<timing>\n"
+ + "<!-- allow for 5 minutes out of every 30 minutes -->\n"
+ + "<timeWindowSecs>1800</timeWindowSecs>\n"
+ + "<timeMaxSecs>300</timeMaxSecs>\n"
+ + "<timeMinSecs>60</timeMinSecs>\n"
+ + "</timing>\n"
+ + "<refreshRate>\n"
+ + "<minimum>120</minimum>\n"
+ + "<maximum>120</maximum>\n"
+ + "</refreshRate>\n"
+ + "<thermalStatusLimit>light</thermalStatusLimit>\n"
+ + "<allowInLowPowerMode>false</allowInLowPowerMode>\n"
+ + "</highBrightnessMode>\n"
+ + "<ambientBrightnessChangeThresholds>\n"
+ + "<brighteningThresholds>\n"
+ + "<minimum>10</minimum>\n"
+ + "</brighteningThresholds>\n"
+ + "<darkeningThresholds>\n"
+ + "<minimum>2</minimum>\n"
+ + "</darkeningThresholds>\n"
+ + "</ambientBrightnessChangeThresholds>\n"
+ + "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> "
+ + "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease> "
+ + "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>"
+ + "<screenBrightnessRampSlowIncrease>0.04</screenBrightnessRampSlowIncrease>"
+ + "<screenBrightnessRampIncreaseMaxMillis>"
+ + "2000"
+ + "</screenBrightnessRampIncreaseMaxMillis>"
+ + "<screenBrightnessRampDecreaseMaxMillis>"
+ + "3000"
+ + "</screenBrightnessRampDecreaseMaxMillis>"
+ + "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
+ + "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
+ + "<displayBrightnessChangeThresholds>"
+ + "<brighteningThresholds>"
+ + "<minimum>"
+ + "0.001"
+ + "</minimum>"
+ + "</brighteningThresholds>"
+ + "<darkeningThresholds>"
+ + "<minimum>"
+ + "0.002"
+ + "</minimum>"
+ + "</darkeningThresholds>"
+ + "</displayBrightnessChangeThresholds>"
+ + "<screenBrightnessRampIncreaseMaxMillis>"
+ + "2000"
+ + "</screenBrightnessRampIncreaseMaxMillis>\n"
+ + "<thermalThrottling>\n"
+ + "<brightnessThrottlingMap>\n"
+ + "<brightnessThrottlingPoint>\n"
+ + "<thermalStatus>emergency</thermalStatus>\n"
+ + "<!-- Throttling to 250 nits: (250-2.0)/(500-2.0)*(0.62-0.0)+0"
+ + ".0 = 0.30875502 -->\n"
+ + "<brightness>0.30875502</brightness>\n"
+ + "</brightnessThrottlingPoint>\n"
+ + "</brightnessThrottlingMap>\n"
+ + "</thermalThrottling>\n"
+ + "</displayConfiguration>\n";
+ }
+
+ private void mockDeviceConfigs() {
+ when(mResources.getFloat(com.android.internal.R.dimen
+ .config_screenBrightnessSettingDefaultFloat)).thenReturn(0.5f);
+ when(mResources.getFloat(com.android.internal.R.dimen
+ .config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 11a7c7d..1f07b20 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -47,8 +47,10 @@
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -1402,6 +1404,28 @@
win.setHasSurface(false);
}
+ @Test
+ public void testCalculateSystemGestureExclusion_unrestricted() throws Exception {
+ mWm.mConstants.mSystemGestureExcludedByPreQStickyImmersive = true;
+
+ final DisplayContent dc = createNewDisplay();
+ final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
+ win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+ win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ win.getAttrs().privateFlags |= PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
+ win.setSystemGestureExclusion(Collections.singletonList(dc.getBounds()));
+
+ performLayout(dc);
+
+ win.setHasSurface(true);
+
+ final Region expected = Region.obtain();
+ expected.set(dc.getBounds());
+ assertEquals(expected, calculateSystemGestureExclusion(dc));
+
+ win.setHasSurface(false);
+ }
+
@UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY})
@Test
public void testRequestResizeForEmptyFrames() {
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 f2640d2..324e244 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1496,79 +1496,6 @@
}
@Test
- public void testComputeConfigResourceOverrides_unresizableApp() {
- // Set up a display in landscape and ignoring orientation request.
- setUpDisplaySizeWithApp(2800, 1400);
- mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-
- prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
-
- final Rect activityBounds = new Rect(mActivity.getBounds());
-
- int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp;
- int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp;
-
- // App should launch in fixed orientation letterbox.
- // Activity bounds should be 700x1400 with the ratio as the display.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
- assertFitted();
- assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
- assertTrue(originalScreenWidthDp < originalScreenHeighthDp);
-
- // Rotate display to portrait.
- rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
-
- // After we rotate, the activity should go in the size-compat mode and report the same
- // configuration values.
- assertScaled();
- assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
- assertEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
- assertEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
-
- // Restart activity
- mActivity.restartProcessIfVisible();
-
- // Now configuration should be updated
- assertFitted();
- assertNotEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
- assertNotEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
- assertEquals(mActivity.getConfiguration().screenWidthDp,
- mActivity.getConfiguration().smallestScreenWidthDp);
- }
-
- @Test
- public void testComputeConfigResourceOverrides_resizableFixedOrientationActivity() {
- // Set up a display in landscape and ignoring orientation request.
- setUpDisplaySizeWithApp(2800, 1400);
- mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-
- // Portrait fixed app without max aspect.
- prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, false /* isUnresizable */);
-
- final Rect activityBounds = new Rect(mActivity.getBounds());
-
- int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp;
- int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp;
-
- // App should launch in fixed orientation letterbox.
- // Activity bounds should be 700x1400 with the ratio as the display.
- assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
- assertFitted();
- assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
- assertTrue(originalScreenWidthDp < originalScreenHeighthDp);
-
- // Rotate display to portrait.
- rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
-
- // Now configuration should be updated
- assertFitted();
- assertNotEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
- assertNotEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
- assertEquals(mActivity.getConfiguration().screenWidthDp,
- mActivity.getConfiguration().smallestScreenWidthDp);
- }
-
- @Test
public void testSplitAspectRatioForUnresizablePortraitApps() {
// Set up a display in landscape and ignoring orientation request.
int screenWidth = 1600;
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 1f03039..f4323db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -705,8 +705,7 @@
final ActivityRecord.CompatDisplayInsets compatInsets =
new ActivityRecord.CompatDisplayInsets(
display, activity, /* fixedOrientationBounds= */ null);
- task.computeConfigResourceOverrides(
- inOutConfig, parentConfig, compatInsets, /* areBoundsLetterboxed */ true);
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatInsets);
assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds());
final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 1715a29..c8ea70c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -22,8 +22,8 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1;
-import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_2;
+import static android.view.InsetsState.ITYPE_BOTTOM_GENERIC_OVERLAY;
+import static android.view.InsetsState.ITYPE_TOP_GENERIC_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -1302,40 +1302,42 @@
TYPE_BASE_APPLICATION);
attrs2.setTitle("AppWindow2");
activity2.addWindow(createWindowState(attrs2, activity2));
- Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
- Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
+ Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
+ Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
- rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
- container.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_2});
+ rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect1,
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
+ container.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect2,
+ new int[]{ITYPE_BOTTOM_GENERIC_OVERLAY});
- InsetsSource navigationBarInsetsProvider1Source = new InsetsSource(
- ITYPE_LOCAL_NAVIGATION_BAR_1);
- navigationBarInsetsProvider1Source.setFrame(navigationBarInsetsRect1);
- navigationBarInsetsProvider1Source.setVisible(true);
- InsetsSource navigationBarInsetsProvider2Source = new InsetsSource(
- ITYPE_LOCAL_NAVIGATION_BAR_2);
- navigationBarInsetsProvider2Source.setFrame(navigationBarInsetsRect2);
- navigationBarInsetsProvider2Source.setVisible(true);
+ InsetsSource genericOverlayInsetsProvider1Source = new InsetsSource(
+ ITYPE_TOP_GENERIC_OVERLAY);
+ genericOverlayInsetsProvider1Source.setFrame(genericOverlayInsetsRect1);
+ genericOverlayInsetsProvider1Source.setVisible(true);
+ InsetsSource genericOverlayInsetsProvider2Source = new InsetsSource(
+ ITYPE_BOTTOM_GENERIC_OVERLAY);
+ genericOverlayInsetsProvider2Source.setFrame(genericOverlayInsetsRect2);
+ genericOverlayInsetsProvider2Source.setVisible(true);
activity0.forAllWindows(window -> {
- assertEquals(navigationBarInsetsRect1,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
+ assertEquals(genericOverlayInsetsRect1,
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
assertEquals(null,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2));
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY));
}, true);
activity1.forAllWindows(window -> {
- assertEquals(navigationBarInsetsRect1,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
- assertEquals(navigationBarInsetsRect2,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+ assertEquals(genericOverlayInsetsRect1,
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
+ assertEquals(genericOverlayInsetsRect2,
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+ .getFrame());
}, true);
activity2.forAllWindows(window -> {
- assertEquals(navigationBarInsetsRect1,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
- assertEquals(navigationBarInsetsRect2,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+ assertEquals(genericOverlayInsetsRect1,
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
+ assertEquals(genericOverlayInsetsRect2,
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+ .getFrame());
}, true);
}
@@ -1344,7 +1346,7 @@
/*
___ rootTask ________________________________________
| | |
- activity0 navigationBarInsetsProvider1 navigationBarInsetsProvider2
+ activity0 genericOverlayInsetsProvider1 genericOverlayInsetsProvider2
*/
final Task rootTask = createTask(mDisplayContent);
@@ -1355,22 +1357,22 @@
attrs.setTitle("AppWindow0");
activity0.addWindow(createWindowState(attrs, activity0));
- Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
- Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
+ Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
+ Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
- rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect1,
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
activity0.forAllWindows(window -> {
- assertEquals(navigationBarInsetsRect1,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
+ assertEquals(genericOverlayInsetsRect1,
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
}, true);
- rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect2,
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
activity0.forAllWindows(window -> {
- assertEquals(navigationBarInsetsRect2,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
+ assertEquals(genericOverlayInsetsRect2,
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
}, true);
}
@@ -1412,30 +1414,32 @@
Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
container.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_2});
+ new int[]{ITYPE_BOTTOM_GENERIC_OVERLAY});
mDisplayContent.getInsetsStateController().onPostLayout();
- rootTask.removeLocalInsetsSourceProvider(new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ rootTask.removeLocalInsetsSourceProvider(new int[]{ITYPE_TOP_GENERIC_OVERLAY});
mDisplayContent.getInsetsStateController().onPostLayout();
activity0.forAllWindows(window -> {
assertEquals(null,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY));
assertEquals(null,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2));
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY));
}, true);
activity1.forAllWindows(window -> {
assertEquals(null,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY));
assertEquals(navigationBarInsetsRect2,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+ .getFrame());
}, true);
activity2.forAllWindows(window -> {
assertEquals(null,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY));
assertEquals(navigationBarInsetsRect2,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+ .getFrame());
}, true);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 5407412..84c2c55 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -30,7 +30,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
-import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1;
+import static android.view.InsetsState.ITYPE_TOP_GENERIC_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -779,11 +779,11 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
.toWindowContainerToken(), navigationBarInsetsProviderRect,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders
- .valueAt(0).getSource().getType()).isEqualTo(ITYPE_LOCAL_NAVIGATION_BAR_1);
+ .valueAt(0).getSource().getType()).isEqualTo(ITYPE_TOP_GENERIC_OVERLAY);
}
@Test
@@ -799,12 +799,12 @@
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
.toWindowContainerToken(), navigationBarInsetsProviderRect,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
final WindowContainerTransaction wct2 = new WindowContainerTransaction();
wct2.removeInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
- .toWindowContainerToken(), new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ .toWindowContainerToken(), new int[]{ITYPE_TOP_GENERIC_OVERLAY});
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct2);
assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders.size()).isEqualTo(0);