Merge "Collect tasks when windowing mode of parent display changes" 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/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index dac55ae..8db298f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1325,7 +1325,8 @@
* flashlight brightness level via
* {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.
* If this value is equal to 1, flashlight brightness control is not supported.
- * The value for this key will be null for devices with no flash unit.</p>
+ * The value for this key will be null for devices with no flash unit.
+ * This level must be set to a safe value to prevent any burn out issues.</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
*/
@PublicKey
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/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index 61f524f..c4d3070 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -80,11 +80,6 @@
void setAnimationTargetsBehindSystemBars(boolean behindSystemBars);
/**
- * Hides the current input method if one is showing.
- */
- void hideCurrentInputMethod();
-
- /**
* Clean up the screenshot of previous task which was created during recents animation that
* was cancelled by a stack order change.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8ec32a6..7be8dff 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3759,6 +3759,7 @@
}
}
mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
+ mOnBackInvokedDispatcher.onWindowFocusChanged(hasWindowFocus);
// NOTE: there's no view visibility (appeared / disapparead) events when the windows focus
// is lost, so we don't need to to force a flush - there might be other events such as
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 23393ff..2268bef 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6440,9 +6440,8 @@
public void setText(CharSequence text, BufferType type) {
setText(text, type, true, 0);
- if (mCharWrapper != null) {
- mCharWrapper.mChars = null;
- }
+ // drop any potential mCharWrappper leaks
+ mCharWrapper = null;
}
@UnsupportedAppUsage
@@ -6653,11 +6652,14 @@
* since the TextView has no way to know that the text
* has changed and that it needs to invalidate and re-layout.
*
+ * @throws NullPointerException if text is null
+ * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length
+ *
* @param text char array to be displayed
* @param start start index in the char array
* @param len length of char count after {@code start}
*/
- public final void setText(char[] text, int start, int len) {
+ public final void setText(/* @NonNull */ char[] text, int start, int len) {
int oldlen = 0;
if (start < 0 || len < 0 || start + len > text.length) {
@@ -13888,16 +13890,17 @@
}
private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
+ @NonNull
private char[] mChars;
private int mStart, mLength;
- public CharWrapper(char[] chars, int start, int len) {
+ CharWrapper(@NonNull char[] chars, int start, int len) {
mChars = chars;
mStart = start;
mLength = len;
}
- /* package */ void set(char[] chars, int start, int len) {
+ /* package */ void set(@NonNull char[] chars, int start, int len) {
mChars = chars;
mStart = start;
mLength = len;
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 5924844..f1a052b 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -81,8 +81,10 @@
@OnBackInvokedDispatcher.Priority int priority,
@NonNull OnBackInvokedCallback callback) {
final Bundle bundle = new Bundle();
+ // Always invoke back for ime without checking the window focus.
final IOnBackInvokedCallback iCallback =
- new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback);
+ new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback,
+ () -> true);
bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
bundle.putInt(RESULT_KEY_PRIORITY, priority);
bundle.putInt(RESULT_KEY_ID, callback.hashCode());
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index d147524d..02c5ebc 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -32,6 +32,7 @@
import java.util.HashMap;
import java.util.Objects;
import java.util.TreeMap;
+import java.util.function.Supplier;
/**
* Provides window based implementation of {@link OnBackInvokedDispatcher}.
@@ -64,6 +65,7 @@
private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
mOnBackInvokedCallbacks = new TreeMap<>();
private final Checker mChecker;
+ private boolean mHasFocus;
public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) {
mChecker = new Checker(applicationCallBackEnabled);
@@ -189,7 +191,7 @@
.ImeOnBackInvokedCallback
? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
callback).getIOnBackInvokedCallback()
- : new OnBackInvokedCallbackWrapper(callback);
+ : new OnBackInvokedCallbackWrapper(callback, this::hasFocus);
callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority);
}
mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
@@ -198,6 +200,17 @@
}
}
+ /**
+ * Called when window focus changed.
+ */
+ public void onWindowFocusChanged(boolean hasFocus) {
+ mHasFocus = hasFocus;
+ }
+
+ private boolean hasFocus() {
+ return mHasFocus;
+ }
+
public OnBackInvokedCallback getTopCallback() {
if (mAllCallbacks.isEmpty()) {
return null;
@@ -221,9 +234,11 @@
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback;
-
- OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
+ private final Supplier<Boolean> mHasFocus;
+ OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback,
+ @NonNull Supplier<Boolean> hasFocus) {
mCallback = new WeakReference<>(callback);
+ mHasFocus = hasFocus;
}
@Override
@@ -263,7 +278,10 @@
if (callback == null) {
return;
}
-
+ if (!mHasFocus.get()) {
+ Log.w(TAG, "Skip back invoke due to current focus has lost.");
+ return;
+ }
callback.onBackInvoked();
});
}
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/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index 47ce2d8..cc4fbab 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -304,6 +304,23 @@
assertFalse(mTextView.isCursorVisible());
}
+ @Test(expected = NullPointerException.class)
+ @UiThreadTest
+ public void setTextCharArrayNullThrows() {
+ mTextView = new TextView(mActivity);
+ mTextView.setText((char[]) null, 0, 0);
+ }
+
+ @Test
+ @UiThreadTest
+ public void setTextCharArrayValidAfterSetTextString() {
+ mTextView = new TextView(mActivity);
+ mTextView.setText(new char[] { 'h', 'i'}, 0, 2);
+ CharSequence charWrapper = mTextView.getText();
+ mTextView.setText("null out char wrapper");
+ assertEquals("hi", charWrapper.toString());
+ }
+
private String createLongText() {
int size = 600 * 1000;
final StringBuilder builder = new StringBuilder(size);
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f448cb3..b5194f6 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -66,6 +66,7 @@
MockitoAnnotations.initMocks(this);
mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */);
mDispatcher.attachToWindow(mWindowSession, mWindow);
+ mDispatcher.onWindowFocusChanged(true);
}
private void waitForIdle() {
@@ -152,4 +153,31 @@
waitForIdle();
verify(mCallback2).onBackStarted();
}
+
+ @Test
+ public void skipBackInvokeWhenNoFocus() throws RemoteException {
+ ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
+ ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+
+ mDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
+
+ verify(mWindowSession, times(1)).setOnBackInvokedCallbackInfo(
+ Mockito.eq(mWindow),
+ captor.capture());
+
+ verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
+
+ // Should invoke back if it's still in focused.
+ captor.getValue().getCallback().onBackInvoked();
+ waitForIdle();
+ verify(mCallback1).onBackInvoked();
+
+ // In case the focus has lost during back gesture.
+ mDispatcher.onWindowFocusChanged(false);
+
+ captor.getValue().getCallback().onBackInvoked();
+ waitForIdle();
+ verifyZeroInteractions(mCallback1);
+ }
}
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 84e949a..f8c015f 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -932,7 +932,7 @@
<font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
</family>
<family lang="und-Laoo" variant="compact">
- <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf
+ <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
</family>
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/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 8c0affb..86f9d5b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -29,6 +29,13 @@
public interface BackAnimation {
/**
+ * Returns a binder that can be passed to an external process to update back animations.
+ */
+ default IBackAnimation createExternalInterface() {
+ return null;
+ }
+
+ /**
* Called when a {@link MotionEvent} is generated by a back gesture.
*
* @param touchX the X touch position of the {@link MotionEvent}.
@@ -47,13 +54,6 @@
void setTriggerBack(boolean triggerBack);
/**
- * Returns a binder that can be passed to an external process to update back animations.
- */
- default IBackAnimation createExternalInterface() {
- return null;
- }
-
- /**
* Sets the threshold values that defining edge swipe behavior.
* @param triggerThreshold the min threshold to trigger back.
* @param progressThreshold the max threshold to keep progressing back animation.
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/compatui/CompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
deleted file mode 100644
index b87cf47..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
+++ /dev/null
@@ -1,35 +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.wm.shell.compatui;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage compat UI.
- */
-@ExternalThread
-public interface CompatUI {
- /**
- * Called when the keyguard showing state changes. Removes all compat UIs if the
- * keyguard is now showing.
- *
- * <p>Note that if the keyguard is occluded it will also be considered showing.
- *
- * @param showing indicates if the keyguard is now showing.
- */
- void onKeyguardShowingChanged(boolean showing);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 99b32a6..db8d9d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -39,9 +39,10 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.transition.Transitions;
import java.lang.ref.WeakReference;
@@ -58,7 +59,7 @@
* activities are in compatibility mode.
*/
public class CompatUIController implements OnDisplaysChangedListener,
- DisplayImeController.ImePositionProcessor {
+ DisplayImeController.ImePositionProcessor, KeyguardChangeListener {
/** Callback for compat UI interaction. */
public interface CompatUICallback {
@@ -100,13 +101,13 @@
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
private final Context mContext;
+ private final ShellController mShellController;
private final DisplayController mDisplayController;
private final DisplayInsetsController mDisplayInsetsController;
private final DisplayImeController mImeController;
private final SyncTransactionQueue mSyncQueue;
private final ShellExecutor mMainExecutor;
private final Lazy<Transitions> mTransitionsLazy;
- private final CompatUIImpl mImpl = new CompatUIImpl();
private CompatUICallback mCallback;
@@ -118,6 +119,7 @@
private boolean mKeyguardShowing;
public CompatUIController(Context context,
+ ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
DisplayImeController imeController,
@@ -125,6 +127,7 @@
ShellExecutor mainExecutor,
Lazy<Transitions> transitionsLazy) {
mContext = context;
+ mShellController = shellController;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
mImeController = imeController;
@@ -134,11 +137,7 @@
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
mCompatUIHintsState = new CompatUIHintsState();
- }
-
- /** Returns implementation of {@link CompatUI}. */
- public CompatUI asCompatUI() {
- return mImpl;
+ shellController.addKeyguardChangeListener(this);
}
/** Sets the callback for UI interactions. */
@@ -223,9 +222,10 @@
layout -> layout.updateVisibility(showOnDisplay(displayId)));
}
- @VisibleForTesting
- void onKeyguardShowingChanged(boolean showing) {
- mKeyguardShowing = showing;
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
+ mKeyguardShowing = visible;
// Hide the compat UIs when keyguard is showing.
forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId())));
}
@@ -373,19 +373,6 @@
}
}
- /**
- * The interface for calls from outside the Shell, within the host process.
- */
- @ExternalThread
- private class CompatUIImpl implements CompatUI {
- @Override
- public void onKeyguardShowingChanged(boolean showing) {
- mMainExecutor.execute(() -> {
- CompatUIController.this.onKeyguardShowingChanged(showing);
- });
- }
- }
-
/** An implementation of {@link OnInsetsChangedListener} for a given display id. */
private class PerDisplayOnInsetsChangedListener implements OnInsetsChangedListener {
final int mDisplayId;
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 ed5e247..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;
@@ -57,15 +55,12 @@
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
-import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
-import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.onehanded.OneHanded;
@@ -85,8 +80,6 @@
import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInterface;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
@@ -173,12 +166,6 @@
@WMSingleton
@Provides
- static Optional<DragAndDrop> provideDragAndDrop(DragAndDropController dragAndDropController) {
- return Optional.of(dragAndDropController.asDragAndDrop());
- }
-
- @WMSingleton
- @Provides
static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
Context context,
CompatUIController compatUI,
@@ -207,18 +194,14 @@
}
@WMSingleton
- @Provides static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) {
- return Optional.of(compatUIController.asCompatUI());
- }
-
- @WMSingleton
@Provides
static CompatUIController provideCompatUIController(Context context,
+ ShellController shellController,
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
@ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
- return new CompatUIController(context, displayController, displayInsetsController,
- imeController, syncQueue, mainExecutor, transitionsLazy);
+ return new CompatUIController(context, shellController, displayController,
+ displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy);
}
@WMSingleton
@@ -375,13 +358,6 @@
@WMSingleton
@Provides
- static Optional<HideDisplayCutout> provideHideDisplayCutout(
- Optional<HideDisplayCutoutController> hideDisplayCutoutController) {
- return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout());
- }
-
- @WMSingleton
- @Provides
static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
ShellController shellController, DisplayController displayController,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -416,23 +392,6 @@
}
//
- // Task to Surface communication
- //
-
- @WMSingleton
- @Provides
- static Optional<TaskSurfaceHelper> provideTaskSurfaceHelper(
- Optional<TaskSurfaceHelperController> taskSurfaceController) {
- return taskSurfaceController.map((controller) -> controller.asTaskSurfaceHelper());
- }
-
- @Provides
- static Optional<TaskSurfaceHelperController> provideTaskSurfaceHelperController(
- ShellTaskOrganizer taskOrganizer, @ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(new TaskSurfaceHelperController(taskOrganizer, mainExecutor));
- }
-
- //
// Pip (optional feature)
//
@@ -663,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,
@@ -687,7 +642,8 @@
Transitions transitions,
StartingWindowController startingWindow,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellInitImpl(displayController,
+ return new ShellInit(shellController,
+ displayController,
displayImeController,
displayInsetsController,
dragAndDropController,
@@ -707,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,
@@ -728,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/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fb51473..0f33f4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -225,6 +225,7 @@
@Provides
@DynamicOverride
static SplitScreenController provideSplitScreenController(
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@@ -234,7 +235,7 @@
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, IconProvider iconProvider,
Optional<RecentTasksController> recentTasks) {
- return new SplitScreenController(shellTaskOrganizer, syncQueue, context,
+ return new SplitScreenController(shellController, shellTaskOrganizer, syncQueue, context,
rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
recentTasks);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java
deleted file mode 100644
index 0335187..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2020 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.draganddrop;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface for telling DragAndDrop stuff.
- */
-@ExternalThread
-public interface DragAndDrop {
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 0d0961c..c5df53b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -81,11 +81,9 @@
private final IconProvider mIconProvider;
private SplitScreenController mSplitScreen;
private ShellExecutor mMainExecutor;
- private DragAndDropImpl mImpl;
private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
- private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
/**
* Listener called during drag events, currently just onDragStarted.
@@ -107,11 +105,6 @@
mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
mMainExecutor = mainExecutor;
- mImpl = new DragAndDropImpl();
- }
-
- public DragAndDrop asDragAndDrop() {
- return mImpl;
}
public void initialize(Optional<SplitScreenController> splitscreen) {
@@ -353,8 +346,4 @@
dragLayout = dl;
}
}
-
- private class DragAndDropImpl implements DragAndDrop {
- // TODO: To be removed
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
deleted file mode 100644
index dd1c8d6..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2020 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.hidedisplaycutout;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * Interface to engage hide display cutout feature.
- */
-@ExternalThread
-public interface HideDisplayCutout {
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index b091ab8..665b035 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -40,8 +40,6 @@
private final Context mContext;
private final ShellController mShellController;
private final HideDisplayCutoutOrganizer mOrganizer;
- private final ShellExecutor mMainExecutor;
- private final HideDisplayCutoutImpl mImpl = new HideDisplayCutoutImpl();
@VisibleForTesting
boolean mEnabled;
@@ -62,23 +60,18 @@
HideDisplayCutoutOrganizer organizer =
new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
- return new HideDisplayCutoutController(context, shellController, organizer, mainExecutor);
+ return new HideDisplayCutoutController(context, shellController, organizer);
}
HideDisplayCutoutController(Context context, ShellController shellController,
- HideDisplayCutoutOrganizer organizer, ShellExecutor mainExecutor) {
+ HideDisplayCutoutOrganizer organizer) {
mContext = context;
mShellController = shellController;
mOrganizer = organizer;
- mMainExecutor = mainExecutor;
updateStatus();
mShellController.addConfigurationChangeListener(this);
}
- public HideDisplayCutout asHideDisplayCutout() {
- return mImpl;
- }
-
@VisibleForTesting
void updateStatus() {
// The config value is used for controlling enabling/disabling status of the feature and is
@@ -112,8 +105,4 @@
pw.println(mEnabled);
mOrganizer.dump(pw);
}
-
- private class HideDisplayCutoutImpl implements HideDisplayCutout {
- // TODO: To be removed
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index ee99f327..76c0f41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -85,9 +85,4 @@
* Notifies when user switch complete
*/
void onUserSwitch(int userId);
-
- /**
- * Notifies when keyguard visibility changed
- */
- void onKeyguardVisibilityChanged(boolean showing);
}
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 ea2d508..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
@@ -55,6 +55,7 @@
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import java.io.PrintWriter;
@@ -63,7 +64,8 @@
* Manages and manipulates the one handed states, transitions, and gesture for phones.
*/
public class OneHandedController implements RemoteCallable<OneHandedController>,
- DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener {
+ DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener,
+ KeyguardChangeListener {
private static final String TAG = "OneHandedController";
private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
@@ -279,6 +281,7 @@
mState.addSListeners(mTutorialHandler);
mShellController.addConfigurationChangeListener(this);
+ mShellController.addKeyguardChangeListener(this);
}
public OneHanded asOneHanded() {
@@ -605,8 +608,11 @@
mTutorialHandler.onConfigurationChanged();
}
- private void onKeyguardVisibilityChanged(boolean showing) {
- mKeyguardShowing = showing;
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
+ mKeyguardShowing = visible;
+ stopOneHanded();
}
private void onUserSwitch(int newUserId) {
@@ -675,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);
}
/**
@@ -756,13 +765,6 @@
OneHandedController.this.onUserSwitch(userId);
});
}
-
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mMainExecutor.execute(() -> {
- OneHandedController.this.onKeyguardVisibilityChanged(showing);
- });
- }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 2ac1129..38631ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -101,23 +101,6 @@
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
/**
- * Called when the visibility of keyguard is changed.
- * @param showing {@code true} if keyguard is now showing, {@code false} otherwise.
- * @param animating {@code true} if system is animating between keyguard and surface behind,
- * this only makes sense when showing is {@code false}.
- */
- default void onKeyguardVisibilityChanged(boolean showing, boolean animating) { }
-
- /**
- * Called when the dismissing animation keyguard and surfaces behind is finished.
- * See also {@link #onKeyguardVisibilityChanged(boolean, boolean)}.
- *
- * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of
- * keyguard dismiss animation.
- */
- default void onKeyguardDismissAnimationFinished() { }
-
- /**
* Dump the current state and information if need.
*
* @param pw The stream to dump information to.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 5ae4602..420d606 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -88,6 +88,7 @@
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.transition.Transitions;
@@ -102,7 +103,7 @@
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipController implements PipTransitionController.PipTransitionCallback,
- RemoteCallable<PipController>, ConfigurationChangeListener {
+ RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener {
private static final String TAG = "PipController";
private Context mContext;
@@ -527,6 +528,7 @@
});
mShellController.addConfigurationChangeListener(this);
+ mShellController.addKeyguardChangeListener(this);
}
@Override
@@ -661,21 +663,24 @@
* finished first to reset the visibility of PiP window.
* See also {@link #onKeyguardDismissAnimationFinished()}
*/
- private void onKeyguardVisibilityChanged(boolean keyguardShowing, boolean animating) {
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
if (!mPipTaskOrganizer.isInPip()) {
return;
}
- if (keyguardShowing) {
+ if (visible) {
mIsKeyguardShowingOrAnimating = true;
hidePipMenu(null /* onStartCallback */, null /* onEndCallback */);
mPipTaskOrganizer.setPipVisibility(false);
- } else if (!animating) {
+ } else if (!animatingDismiss) {
mIsKeyguardShowingOrAnimating = false;
mPipTaskOrganizer.setPipVisibility(true);
}
}
- private void onKeyguardDismissAnimationFinished() {
+ @Override
+ public void onKeyguardDismissAnimationFinished() {
if (mPipTaskOrganizer.isInPip()) {
mIsKeyguardShowingOrAnimating = false;
mPipTaskOrganizer.setPipVisibility(true);
@@ -996,18 +1001,6 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing, boolean animating) {
- mMainExecutor.execute(() -> {
- PipController.this.onKeyguardVisibilityChanged(showing, animating);
- });
- }
-
- @Override
- public void onKeyguardDismissAnimationFinished() {
- mMainExecutor.execute(PipController.this::onKeyguardDismissAnimationFinished);
- }
-
- @Override
public void dump(PrintWriter pw) {
try {
mMainExecutor.executeBlocking(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 29b6311..e73b799 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -77,12 +77,6 @@
return null;
}
- /**
- * Called when the visibility of the keyguard changes.
- * @param showing Indicates if the keyguard is now visible.
- */
- void onKeyguardVisibilityChanged(boolean showing);
-
/** Called when device waking up finished. */
void onFinishedWakingUp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 21bea46..53ec39d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -80,6 +80,8 @@
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -99,7 +101,7 @@
*/
// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
public class SplitScreenController implements DragAndDropPolicy.Starter,
- RemoteCallable<SplitScreenController> {
+ RemoteCallable<SplitScreenController>, KeyguardChangeListener {
private static final String TAG = SplitScreenController.class.getSimpleName();
public static final int EXIT_REASON_UNKNOWN = 0;
@@ -127,6 +129,7 @@
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
+ private final ShellController mShellController;
private final ShellTaskOrganizer mTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Context mContext;
@@ -147,7 +150,8 @@
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mSplitTasksContainerLayer;
- public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
+ public SplitScreenController(ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
ShellExecutor mainExecutor, DisplayController displayController,
@@ -155,6 +159,7 @@
DisplayInsetsController displayInsetsController,
Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
Optional<RecentTasksController> recentTasks) {
+ mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
@@ -185,6 +190,7 @@
}
public void onOrganizerRegistered() {
+ mShellController.addKeyguardChangeListener(this);
if (mStageCoordinator == null) {
// TODO: Multi-display
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
@@ -283,8 +289,10 @@
mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
}
- public void onKeyguardVisibilityChanged(boolean showing) {
- mStageCoordinator.onKeyguardVisibilityChanged(showing);
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
+ mStageCoordinator.onKeyguardVisibilityChanged(visible);
}
public void onFinishedWakingUp() {
@@ -658,13 +666,6 @@
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onKeyguardVisibilityChanged(showing);
- });
- }
-
- @Override
public void onFinishedWakingUp() {
mMainExecutor.execute(() -> {
SplitScreenController.this.onFinishedWakingUp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
new file mode 100644
index 0000000..1c0b358
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
@@ -0,0 +1,36 @@
+/*
+ * 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.wm.shell.sysui;
+
+/**
+ * Callbacks for when the keyguard changes.
+ */
+public interface KeyguardChangeListener {
+ /**
+ * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded).
+ */
+ default void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {}
+
+ /**
+ * Notifies the Shell when the keyguard dismiss animation has finished.
+ *
+ * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of
+ * keyguard dismiss animation.
+ */
+ default void onKeyguardDismissAnimationFinished() {}
+}
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 28a30f4..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,7 +47,12 @@
private final ShellExecutor mMainExecutor;
private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
- private final CopyOnWriteArrayList<ConfigurationChangeListener> mListeners =
+ private ShellInit mShellInit;
+ private ShellCommandHandler mShellCommandHandler;
+
+ private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
+ new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
new CopyOnWriteArrayList<>();
private Configuration mLastConfiguration;
@@ -64,19 +69,53 @@
}
/**
+ * 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.
*/
public void addConfigurationChangeListener(ConfigurationChangeListener listener) {
- mListeners.remove(listener);
- mListeners.add(listener);
+ mConfigChangeListeners.remove(listener);
+ mConfigChangeListeners.add(listener);
}
/**
* Removes an existing configuration listener.
*/
public void removeConfigurationChangeListener(ConfigurationChangeListener listener) {
- mListeners.remove(listener);
+ mConfigChangeListeners.remove(listener);
+ }
+
+ /**
+ * Adds a new Keyguard listener. The Keyguard change callbacks are not made in any
+ * particular order.
+ */
+ public void addKeyguardChangeListener(KeyguardChangeListener listener) {
+ mKeyguardChangeListeners.remove(listener);
+ mKeyguardChangeListeners.add(listener);
+ }
+
+ /**
+ * Removes an existing Keyguard listener.
+ */
+ public void removeKeyguardChangeListener(KeyguardChangeListener listener) {
+ mKeyguardChangeListeners.remove(listener);
}
@VisibleForTesting
@@ -102,7 +141,7 @@
// Update the last configuration and call listeners
mLastConfiguration.updateFrom(newConfig);
- for (ConfigurationChangeListener listener : mListeners) {
+ for (ConfigurationChangeListener listener : mConfigChangeListeners) {
listener.onConfigurationChanged(newConfig);
if (densityFontScaleChanged) {
listener.onDensityOrFontScaleChanged();
@@ -119,11 +158,26 @@
}
}
+ @VisibleForTesting
+ void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) {
+ for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
+ listener.onKeyguardVisibilityChanged(visible, occluded, animatingDismiss);
+ }
+ }
+
+ @VisibleForTesting
+ void onKeyguardDismissAnimationFinished() {
+ for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
+ listener.onKeyguardDismissAnimationFinished();
+ }
+ }
+
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
- pw.println(innerPrefix + "mListeners=" + mListeners.size());
+ pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size());
pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
+ pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
}
/**
@@ -131,10 +185,56 @@
*/
@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(() ->
ShellController.this.onConfigurationChanged(newConfiguration));
}
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
+ mMainExecutor.execute(() ->
+ ShellController.this.onKeyguardVisibilityChanged(visible, occluded,
+ animatingDismiss));
+ }
+
+ @Override
+ public void onKeyguardDismissAnimationFinished() {
+ mMainExecutor.execute(() ->
+ ShellController.this.onKeyguardDismissAnimationFinished());
+ }
}
}
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 a2d072c..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,16 +18,45 @@
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) {}
+
+ /**
+ * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded) or not
+ * showing, and whether it is animating a dismiss.
+ */
+ default void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {}
+
+ /**
+ * Notifies the Shell when the keyguard dismiss animation has finished.
+ */
+ default void onKeyguardDismissAnimationFinished() {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
deleted file mode 100644
index ad9dda6..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
+++ /dev/null
@@ -1,37 +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.wm.shell.tasksurfacehelper;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Interface to communicate with a Task's SurfaceControl.
- */
-public interface TaskSurfaceHelper {
-
- /** Sets the METADATA_GAME_MODE for the layer corresponding to the task **/
- default void setGameModeForTask(int taskId, int gameMode) {}
-
- /** Takes a screenshot for a task **/
- default void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor,
- Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {}
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
deleted file mode 100644
index 064d9d1..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
+++ /dev/null
@@ -1,82 +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.wm.shell.tasksurfacehelper;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.ShellExecutor;
-
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Intermediary controller that communicates with {@link ShellTaskOrganizer} to send commands
- * to SurfaceControl.
- */
-public class TaskSurfaceHelperController {
-
- private final ShellTaskOrganizer mTaskOrganizer;
- private final ShellExecutor mMainExecutor;
- private final TaskSurfaceHelperImpl mImpl = new TaskSurfaceHelperImpl();
-
- public TaskSurfaceHelperController(ShellTaskOrganizer taskOrganizer,
- ShellExecutor mainExecutor) {
- mTaskOrganizer = taskOrganizer;
- mMainExecutor = mainExecutor;
- }
-
- public TaskSurfaceHelper asTaskSurfaceHelper() {
- return mImpl;
- }
-
- /**
- * Sends a Transaction to set the game mode metadata on the
- * corresponding SurfaceControl
- */
- public void setGameModeForTask(int taskId, int gameMode) {
- mTaskOrganizer.setSurfaceMetadata(taskId, SurfaceControl.METADATA_GAME_MODE, gameMode);
- }
-
- /**
- * Take screenshot of the specified task.
- */
- public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
- Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
- mTaskOrganizer.screenshotTask(taskInfo, crop, consumer);
- }
-
- private class TaskSurfaceHelperImpl implements TaskSurfaceHelper {
- @Override
- public void setGameModeForTask(int taskId, int gameMode) {
- mMainExecutor.execute(() -> {
- TaskSurfaceHelperController.this.setGameModeForTask(taskId, gameMode);
- });
- }
-
- @Override
- public void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor,
- Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
- mMainExecutor.execute(() -> {
- TaskSurfaceHelperController.this.screenshotTask(taskInfo, crop,
- (t) -> executor.execute(() -> consumer.accept(t)));
- });
- }
- }
-}
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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 596100d..828c13e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -53,6 +53,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -78,6 +79,7 @@
private static final int TASK_ID = 12;
private CompatUIController mController;
+ private @Mock ShellController mMockShellController;
private @Mock DisplayController mMockDisplayController;
private @Mock DisplayInsetsController mMockDisplayInsetsController;
private @Mock DisplayLayout mMockDisplayLayout;
@@ -105,7 +107,7 @@
doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
- mController = new CompatUIController(mContext, mMockDisplayController,
+ mController = new CompatUIController(mContext, mMockShellController, mMockDisplayController,
mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor,
mMockTransitionsLazy) {
@Override
@@ -124,6 +126,11 @@
}
@Test
+ public void instantiateController_registerKeyguardChangeListener() {
+ verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+ }
+
+ @Test
public void testListenerRegistered() {
verify(mMockDisplayController).addDisplayWindowListener(mController);
verify(mMockImeController).addPositionProcessor(mController);
@@ -324,7 +331,7 @@
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
// Verify that the restart button is hidden after keyguard becomes showing.
- mController.onKeyguardShowingChanged(true);
+ mController.onKeyguardVisibilityChanged(true, false, false);
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
@@ -340,7 +347,7 @@
false);
// Verify button is shown after keyguard becomes not showing.
- mController.onKeyguardShowingChanged(false);
+ mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
@@ -352,7 +359,7 @@
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
- mController.onKeyguardShowingChanged(true);
+ mController.onKeyguardVisibilityChanged(true, false, false);
verify(mMockCompatLayout, times(2)).updateVisibility(false);
verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
@@ -360,7 +367,7 @@
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
// Verify button remains hidden after keyguard becomes not showing since IME is showing.
- mController.onKeyguardShowingChanged(false);
+ mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
@@ -378,7 +385,7 @@
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
- mController.onKeyguardShowingChanged(true);
+ mController.onKeyguardVisibilityChanged(true, false, false);
verify(mMockCompatLayout, times(2)).updateVisibility(false);
verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
@@ -392,7 +399,7 @@
verify(mMockLetterboxEduLayout).updateVisibility(false);
// Verify button is shown after keyguard becomes not showing.
- mController.onKeyguardShowingChanged(false);
+ mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
index 68e955e..dcc504a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -29,7 +29,6 @@
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sysui.ShellController;
import org.junit.Before;
@@ -49,8 +48,6 @@
private ShellController mShellController;
@Mock
private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
- @Mock
- private ShellExecutor mMockMainExecutor;
private HideDisplayCutoutController mHideDisplayCutoutController;
@@ -58,7 +55,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mHideDisplayCutoutController = new HideDisplayCutoutController(
- mContext, mShellController, mMockDisplayAreaOrganizer, mMockMainExecutor);
+ mContext, mShellController, mMockDisplayAreaOrganizer);
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 958969d..dbf93ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -149,6 +149,11 @@
}
@Test
+ public void testControllerRegistersKeyguardChangeListener() {
+ verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+ }
+
+ @Test
public void testDefaultShouldNotInOneHanded() {
// Assert default transition state is STATE_NONE
assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 827785b..f192514 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -123,6 +123,11 @@
}
@Test
+ public void instantiatePipController_registerKeyguardChangeListener() {
+ verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+ }
+
+ @Test
public void instantiatePipController_registersPipTransitionCallback() {
verify(mMockPipTransitionController).registerPipTransitionCallback(any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index c10e4a1..c7a261f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -28,6 +28,9 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.ComponentName;
@@ -44,10 +47,12 @@
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.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -65,6 +70,7 @@
@RunWith(AndroidJUnit4.class)
public class SplitScreenControllerTests extends ShellTestCase {
+ @Mock ShellController mShellController;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock SyncTransactionQueue mSyncQueue;
@Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@@ -82,10 +88,17 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mSplitScreenController = spy(new SplitScreenController(mTaskOrganizer, mSyncQueue, mContext,
- mRootTDAOrganizer, mMainExecutor, mDisplayController, mDisplayImeController,
- mDisplayInsetsController, mTransitions, mTransactionPool, mIconProvider,
- mRecentTasks));
+ mSplitScreenController = spy(new SplitScreenController(mShellController, mTaskOrganizer,
+ mSyncQueue, mContext, mRootTDAOrganizer, mMainExecutor, mDisplayController,
+ mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks));
+ }
+
+ @Test
+ public void testControllerRegistersKeyguardChangeListener() {
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onOrganizerRegistered();
+ verify(mShellController, times(1)).addKeyguardChangeListener(any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
index 6cc3ae8..1c0e46f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -46,12 +46,14 @@
private ShellExecutor mExecutor;
private ShellController mController;
- private TestConfigurationChangeListener mListener;
+ private TestConfigurationChangeListener mConfigChangeListener;
+ private TestKeyguardChangeListener mKeyguardChangeListener;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mListener = new TestConfigurationChangeListener();
+ mKeyguardChangeListener = new TestKeyguardChangeListener();
+ mConfigChangeListener = new TestConfigurationChangeListener();
mController = new ShellController(mExecutor);
mController.onConfigurationChanged(getConfigurationCopy());
}
@@ -62,47 +64,97 @@
}
@Test
+ public void testAddKeyguardChangeListener_ensureCallback() {
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+ mController.onKeyguardVisibilityChanged(true, false, false);
+ assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+ assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+ }
+
+ @Test
+ public void testDoubleAddKeyguardChangeListener_ensureSingleCallback() {
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+ mController.onKeyguardVisibilityChanged(true, false, false);
+ assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+ assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+ }
+
+ @Test
+ public void testAddRemoveKeyguardChangeListener_ensureNoCallback() {
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+ mController.removeKeyguardChangeListener(mKeyguardChangeListener);
+
+ mController.onKeyguardVisibilityChanged(true, false, false);
+ assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
+ assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+ }
+
+ @Test
+ public void testKeyguardVisibilityChanged() {
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+ mController.onKeyguardVisibilityChanged(true, true, true);
+ assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+ assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
+ assertTrue(mKeyguardChangeListener.lastOccluded);
+ assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
+ assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+ }
+
+ @Test
+ public void testKeyguardDismissAnimationFinished() {
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+ mController.onKeyguardDismissAnimationFinished();
+ assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
+ assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 1);
+ }
+
+ @Test
public void testAddConfigurationChangeListener_ensureCallback() {
- mController.addConfigurationChangeListener(mListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
Configuration newConfig = getConfigurationCopy();
newConfig.densityDpi = 200;
mController.onConfigurationChanged(newConfig);
- assertTrue(mListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.configChanges == 1);
}
@Test
public void testDoubleAddConfigurationChangeListener_ensureSingleCallback() {
- mController.addConfigurationChangeListener(mListener);
- mController.addConfigurationChangeListener(mListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
Configuration newConfig = getConfigurationCopy();
newConfig.densityDpi = 200;
mController.onConfigurationChanged(newConfig);
- assertTrue(mListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.configChanges == 1);
}
@Test
public void testAddRemoveConfigurationChangeListener_ensureNoCallback() {
- mController.addConfigurationChangeListener(mListener);
- mController.removeConfigurationChangeListener(mListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+ mController.removeConfigurationChangeListener(mConfigChangeListener);
Configuration newConfig = getConfigurationCopy();
newConfig.densityDpi = 200;
mController.onConfigurationChanged(newConfig);
- assertTrue(mListener.configChanges == 0);
+ assertTrue(mConfigChangeListener.configChanges == 0);
}
@Test
public void testMultipleConfigurationChangeListeners() {
TestConfigurationChangeListener listener2 = new TestConfigurationChangeListener();
- mController.addConfigurationChangeListener(mListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
mController.addConfigurationChangeListener(listener2);
Configuration newConfig = getConfigurationCopy();
newConfig.densityDpi = 200;
mController.onConfigurationChanged(newConfig);
- assertTrue(mListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.configChanges == 1);
assertTrue(listener2.configChanges == 1);
}
@@ -115,7 +167,7 @@
}
};
mController.addConfigurationChangeListener(badListener);
- mController.addConfigurationChangeListener(mListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
// Ensure we don't fail just because a listener was removed mid-callback
Configuration newConfig = getConfigurationCopy();
@@ -125,77 +177,77 @@
@Test
public void testDensityChangeCallback() {
- mController.addConfigurationChangeListener(mListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
Configuration newConfig = getConfigurationCopy();
newConfig.densityDpi = 200;
mController.onConfigurationChanged(newConfig);
- assertTrue(mListener.configChanges == 1);
- assertTrue(mListener.densityChanges == 1);
- assertTrue(mListener.smallestWidthChanges == 0);
- assertTrue(mListener.themeChanges == 0);
- assertTrue(mListener.localeChanges == 0);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 1);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+ assertTrue(mConfigChangeListener.themeChanges == 0);
+ assertTrue(mConfigChangeListener.localeChanges == 0);
}
@Test
public void testFontScaleChangeCallback() {
- mController.addConfigurationChangeListener(mListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
Configuration newConfig = getConfigurationCopy();
newConfig.fontScale = 2;
mController.onConfigurationChanged(newConfig);
- assertTrue(mListener.configChanges == 1);
- assertTrue(mListener.densityChanges == 1);
- assertTrue(mListener.smallestWidthChanges == 0);
- assertTrue(mListener.themeChanges == 0);
- assertTrue(mListener.localeChanges == 0);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 1);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+ assertTrue(mConfigChangeListener.themeChanges == 0);
+ assertTrue(mConfigChangeListener.localeChanges == 0);
}
@Test
public void testSmallestWidthChangeCallback() {
- mController.addConfigurationChangeListener(mListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
Configuration newConfig = getConfigurationCopy();
newConfig.smallestScreenWidthDp = 100;
mController.onConfigurationChanged(newConfig);
- assertTrue(mListener.configChanges == 1);
- assertTrue(mListener.densityChanges == 0);
- assertTrue(mListener.smallestWidthChanges == 1);
- assertTrue(mListener.themeChanges == 0);
- assertTrue(mListener.localeChanges == 0);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 0);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 1);
+ assertTrue(mConfigChangeListener.themeChanges == 0);
+ assertTrue(mConfigChangeListener.localeChanges == 0);
}
@Test
public void testThemeChangeCallback() {
- mController.addConfigurationChangeListener(mListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
Configuration newConfig = getConfigurationCopy();
newConfig.assetsSeq++;
mController.onConfigurationChanged(newConfig);
- assertTrue(mListener.configChanges == 1);
- assertTrue(mListener.densityChanges == 0);
- assertTrue(mListener.smallestWidthChanges == 0);
- assertTrue(mListener.themeChanges == 1);
- assertTrue(mListener.localeChanges == 0);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 0);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+ assertTrue(mConfigChangeListener.themeChanges == 1);
+ assertTrue(mConfigChangeListener.localeChanges == 0);
}
@Test
public void testNightModeChangeCallback() {
- mController.addConfigurationChangeListener(mListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
Configuration newConfig = getConfigurationCopy();
newConfig.uiMode = Configuration.UI_MODE_NIGHT_YES;
mController.onConfigurationChanged(newConfig);
- assertTrue(mListener.configChanges == 1);
- assertTrue(mListener.densityChanges == 0);
- assertTrue(mListener.smallestWidthChanges == 0);
- assertTrue(mListener.themeChanges == 1);
- assertTrue(mListener.localeChanges == 0);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 0);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+ assertTrue(mConfigChangeListener.themeChanges == 1);
+ assertTrue(mConfigChangeListener.localeChanges == 0);
}
@Test
public void testLocaleChangeCallback() {
- mController.addConfigurationChangeListener(mListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
Configuration newConfig = getConfigurationCopy();
// Just change the locales to be different
@@ -205,11 +257,11 @@
newConfig.locale = Locale.CANADA;
}
mController.onConfigurationChanged(newConfig);
- assertTrue(mListener.configChanges == 1);
- assertTrue(mListener.densityChanges == 0);
- assertTrue(mListener.smallestWidthChanges == 0);
- assertTrue(mListener.themeChanges == 0);
- assertTrue(mListener.localeChanges == 1);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 0);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+ assertTrue(mConfigChangeListener.themeChanges == 0);
+ assertTrue(mConfigChangeListener.localeChanges == 1);
}
private Configuration getConfigurationCopy() {
@@ -253,4 +305,27 @@
localeChanges++;
}
}
+
+ private class TestKeyguardChangeListener implements KeyguardChangeListener {
+ // Counts of number of times each of the callbacks are called
+ public int visibilityChanged;
+ public boolean lastVisibility;
+ public boolean lastOccluded;
+ public boolean lastAnimatingDismiss;
+ public int dismissAnimationFinished;
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
+ lastVisibility = visible;
+ lastOccluded = occluded;
+ lastAnimatingDismiss = animatingDismiss;
+ visibilityChanged++;
+ }
+
+ @Override
+ public void onKeyguardDismissAnimationFinished() {
+ dismissAnimationFinished++;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java
deleted file mode 100644
index 7583418..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java
+++ /dev/null
@@ -1,57 +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.wm.shell.tasksurfacehelper;
-
-import static org.mockito.Mockito.verify;
-
-import android.testing.AndroidTestingRunner;
-import android.view.SurfaceControl;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.ShellExecutor;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class TaskSurfaceHelperControllerTest extends ShellTestCase {
- private TaskSurfaceHelperController mTaskSurfaceHelperController;
- @Mock
- private ShellTaskOrganizer mMockTaskOrganizer;
- @Mock
- private ShellExecutor mMockShellExecutor;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mTaskSurfaceHelperController = new TaskSurfaceHelperController(
- mMockTaskOrganizer, mMockShellExecutor);
- }
-
- @Test
- public void testSetGameModeForTask() {
- mTaskSurfaceHelperController.setGameModeForTask(/*taskId*/1, /*gameMode*/3);
- verify(mMockTaskOrganizer).setSurfaceMetadata(1, SurfaceControl.METADATA_GAME_MODE, 3);
- }
-}
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/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f92cc8e..4f84c8c 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -246,9 +246,9 @@
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] -->
<string name="bluetooth_profile_pan">Internet access</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] -->
- <string name="bluetooth_profile_pbap">Contact sharing</string>
+ <string name="bluetooth_profile_pbap">Contacts and call history sharing</string>
<!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] -->
- <string name="bluetooth_profile_pbap_summary">Use for contact sharing</string>
+ <string name="bluetooth_profile_pbap_summary">Use for contacts and call history sharing</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
<string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. -->
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index fbe3356..8ddd430 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -523,9 +523,10 @@
state: LaunchAnimator.State,
linearProgress: Float,
) {
- if (transactionApplierView.viewRootImpl == null) {
- // If the view root we synchronize with was detached, don't apply any transaction
- // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw).
+ if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
+ // Don't apply any transaction if the view root we synchronize with was detached or
+ // if the SurfaceControl associated with [window] is not valid, as
+ // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw.
return
}
@@ -605,9 +606,10 @@
state: LaunchAnimator.State,
linearProgress: Float
) {
- if (transactionApplierView.viewRootImpl == null) {
- // If the view root we synchronize with was detached, don't apply any transaction
- // (as [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw).
+ if (transactionApplierView.viewRootImpl == null || !navigationBar.leash.isValid) {
+ // Don't apply any transaction if the view root we synchronize with was detached or
+ // if the SurfaceControl associated with [navigationBar] is not valid, as
+ // [SyncRtSurfaceTransactionApplier.scheduleApply] would otherwise throw.
return
}
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/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 47f448d..eb000ad 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -49,17 +49,16 @@
* Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
* whenever possible instead.
*/
-open class GhostedViewLaunchAnimatorController(
+open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
/** The view that will be ghosted and from which the background will be extracted. */
private val ghostedView: View,
/** The [InteractionJankMonitor.CujType] associated to this animation. */
private val cujType: Int? = null,
- private var interactionJankMonitor: InteractionJankMonitor? = null
+ private var interactionJankMonitor: InteractionJankMonitor =
+ InteractionJankMonitor.getInstance(),
) : ActivityLaunchAnimator.Controller {
- constructor(view: View, type: Int) : this(view, type, null)
-
/** The container to which we will add the ghost view and expanding background. */
override var launchContainer = ghostedView.rootView as ViewGroup
private val launchContainerOverlay: ViewGroupOverlay
@@ -203,7 +202,7 @@
val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
matrix.getValues(initialGhostViewMatrixValues)
- cujType?.let { interactionJankMonitor?.begin(ghostedView, it) }
+ cujType?.let { interactionJankMonitor.begin(ghostedView, it) }
}
override fun onLaunchAnimationProgress(
@@ -289,7 +288,7 @@
return
}
- cujType?.let { interactionJankMonitor?.end(it) }
+ cujType?.let { interactionJankMonitor.end(it) }
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
diff --git a/packages/SystemUI/docs/user-file-manager.md b/packages/SystemUI/docs/user-file-manager.md
index 64f1694..52fa206 100644
--- a/packages/SystemUI/docs/user-file-manager.md
+++ b/packages/SystemUI/docs/user-file-manager.md
@@ -1,10 +1,30 @@
# UserFileManager
-This class is used to generate file paths and SharedPreferences that is compatible for multiple
+This class is used to generate file paths and SharedPreferences that is compatible for specific OS
users in SystemUI. Due to constraints in SystemUI, we can only read/write files as the system user.
Therefore, for secondary users, we want to store secondary user specific files into the system user
directory.
+
+## Usages
+
+Inject UserFileManager into your class.
+
+### fun getFile(fileName: String, userId: Int): File
+Add a file name and user id. You can retrieve the current user id from UserTracker. This will
+return a java.io File object that contains the file path to write/read to.
+
+i.e. `fileManager.getFile("example.xml", userTracker.userId)`
+
+### fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences
+Add a file name, user id, and PreferencesMode. You can retrieve the current user id from
+UserTracker. This returns SharedPreferences object that is tied to the specific user. Note that if
+the SharedPreferences file does not exist, one will be created automatically. See
+[SharedPreferences documentation](https://developer.android.com/reference/android/content/Context#getSharedPreferences(java.lang.String,%20int))
+for more details.
+
+i.e. `fileManager.getSharedPreferences("prefs.xml", userTracker.userId, 0)`
+
## Handling User Removal
This class will listen for Intent.ACTION_USER_REMOVED and remove directories that no longer
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
index 3058d94..bef61b8 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
@@ -23,7 +23,9 @@
/**
* Plugin used to replace main clock in keyguard.
+ * @deprecated Migrating to ClockProviderPlugin
*/
+@Deprecated
@ProvidesInterface(action = ClockPlugin.ACTION, version = ClockPlugin.VERSION)
public interface ClockPlugin extends Plugin {
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 99a5a2e..1a1fc75 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -115,6 +115,7 @@
android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
android:textColor="?attr/overlayButtonTextColor"
+ android:textColorLink="?attr/overlayButtonTextColor"
android:background="?androidprv:attr/colorAccentSecondary"
android:layout_width="@dimen/clipboard_preview_size"
android:layout_height="@dimen/clipboard_preview_size"/>
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/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index e47eed9..d27fa19 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -60,9 +60,8 @@
</com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
<FrameLayout android:id="@+id/system_icons_container"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_weight="1"
android:layout_marginEnd="@dimen/status_bar_padding_end"
android:gravity="center_vertical|end">
<include layout="@layout/system_icons" />
diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml
index fa696cc..aadfae8 100644
--- a/packages/SystemUI/res/layout/notification_icon_area.xml
+++ b/packages/SystemUI/res/layout/notification_icon_area.xml
@@ -14,18 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<com.android.keyguard.AlphaOptimizedLinearLayout
+<com.android.systemui.statusbar.phone.NotificationIconContainer
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/notification_icon_area_inner"
- android:layout_width="match_parent"
+ android:id="@+id/notificationIcons"
+ android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:clipChildren="false">
- <com.android.systemui.statusbar.phone.NotificationIconContainer
- android:id="@+id/notificationIcons"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignParentStart="true"
- android:gravity="center_vertical"
- android:orientation="horizontal"
- android:clipChildren="false"/>
-</com.android.keyguard.AlphaOptimizedLinearLayout>
\ No newline at end of file
+ android:clipChildren="false"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index deab1eb..e281511 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -47,52 +47,63 @@
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingEnd="@dimen/status_bar_padding_end"
android:paddingTop="@dimen/status_bar_padding_top"
- android:orientation="horizontal"
- >
+ android:orientation="horizontal">
+
+ <!-- Container for the entire start half of the status bar. It will always use the same
+ width, independent of the number of visible children and sub-children. -->
<FrameLayout
+ android:id="@+id/status_bar_start_side_container"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1">
- <include layout="@layout/heads_up_status_bar_layout" />
+ <!-- Container that is wrapped around the views on the start half of the status bar.
+ Its width will change with the number of visible children and sub-children.
+ It is useful when we want to know the visible bounds of the content. -->
+ <FrameLayout
+ android:id="@+id/status_bar_start_side_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipChildren="false">
- <!-- The alpha of the left side is controlled by PhoneStatusBarTransitions, and the
- individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK and
- DISABLE_NOTIFICATION_ICONS, respectively -->
- <LinearLayout
- android:id="@+id/status_bar_left_side"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:clipChildren="false"
- >
- <ViewStub
- android:id="@+id/operator_name"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout="@layout/operator_name" />
+ <include layout="@layout/heads_up_status_bar_layout" />
- <com.android.systemui.statusbar.policy.Clock
- android:id="@+id/clock"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- android:singleLine="true"
- android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
- android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
- android:gravity="center_vertical|start"
- />
+ <!-- The alpha of the start side is controlled by PhoneStatusBarTransitions, and the
+ individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK
+ and DISABLE_NOTIFICATION_ICONS, respectively -->
+ <LinearLayout
+ android:id="@+id/status_bar_start_side_except_heads_up"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:clipChildren="false">
+ <ViewStub
+ android:id="@+id/operator_name"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout="@layout/operator_name" />
- <include layout="@layout/ongoing_call_chip" />
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ android:singleLine="true"
+ android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+ android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
+ android:gravity="center_vertical|start"
+ />
- <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@+id/notification_icon_area"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:orientation="horizontal"
- android:clipChildren="false"/>
+ <include layout="@layout/ongoing_call_chip" />
- </LinearLayout>
+ <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ android:id="@+id/notification_icon_area"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:clipChildren="false"/>
+
+ </LinearLayout>
+ </FrameLayout>
</FrameLayout>
<!-- Space should cover the notch (if it exists) and let other views lay out around it -->
@@ -103,42 +114,57 @@
android:gravity="center_horizontal|center_vertical"
/>
- <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
+ <!-- Container for the entire end half of the status bar. It will always use the same
+ width, independent of the number of visible children and sub-children. -->
+ <FrameLayout
+ android:id="@+id/status_bar_end_side_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
- android:orientation="horizontal"
- android:gravity="center_vertical|end"
- >
+ android:clipChildren="false">
- <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
- android:id="@+id/user_switcher_container"
+ <!-- Container that is wrapped around the views on the end half of the
+ status bar. Its width will change with the number of visible children and
+ sub-children.
+ It is useful when we want know the visible bounds of the content.-->
+ <com.android.keyguard.AlphaOptimizedLinearLayout
+ android:id="@+id/status_bar_end_side_content"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
android:orientation="horizontal"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:layout_marginEnd="16dp"
- android:background="@drawable/status_bar_user_chip_bg"
- android:visibility="visible" >
- <ImageView android:id="@+id/current_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:scaleType="centerInside"
- android:paddingEnd="4dp" />
+ android:gravity="center_vertical|end"
+ android:clipChildren="false">
- <TextView android:id="@+id/current_user_name"
+ <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+ android:id="@+id/user_switcher_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- />
- </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:layout_marginEnd="16dp"
+ android:background="@drawable/status_bar_user_chip_bg"
+ android:visibility="visible" >
+ <ImageView android:id="@+id/current_user_avatar"
+ android:layout_width="@dimen/multi_user_avatar_keyguard_size"
+ android:layout_height="@dimen/multi_user_avatar_keyguard_size"
+ android:scaleType="centerInside"
+ android:paddingEnd="4dp" />
- <include layout="@layout/system_icons" />
- </com.android.keyguard.AlphaOptimizedLinearLayout>
+ <TextView android:id="@+id/current_user_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ />
+ </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+
+ <include layout="@layout/system_icons" />
+ </com.android.keyguard.AlphaOptimizedLinearLayout>
+ </FrameLayout>
</LinearLayout>
<ViewStub
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/Android.bp b/packages/SystemUI/shared/Android.bp
index 9f790c6..114ea65 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -50,6 +50,7 @@
"SystemUIUnfoldLib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
+ "gson-prebuilt-jar",
"dagger2",
"jsr330",
],
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/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt
new file mode 100644
index 0000000..916a557
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockProviderPlugin.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.shared.clocks
+
+import com.android.systemui.plugins.Plugin
+import com.android.systemui.plugins.annotations.ProvidesInterface
+import android.annotation.FloatRange
+import android.graphics.drawable.Drawable
+import android.view.View
+
+/** Identifies a clock design */
+typealias ClockId = String
+
+/** A Plugin which exposes the ClockProvider interface */
+@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION)
+interface ClockProviderPlugin : Plugin, ClockProvider {
+ companion object {
+ const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER"
+ const val VERSION = 1
+ }
+}
+
+/** Interface for building clocks and providing information about those clocks */
+interface ClockProvider {
+ /** Returns metadata for all clocks this provider knows about */
+ fun getClocks(): List<ClockMetadata>
+
+ /** Initializes and returns the target clock design */
+ fun createClock(id: ClockId): Clock
+
+ /** A static thumbnail for rendering in some examples */
+ fun getClockThumbnail(id: ClockId): Drawable?
+}
+
+/** Interface for controlling an active clock */
+interface Clock {
+ /** A small version of the clock, appropriate for smaller viewports */
+ val smallClock: View
+
+ /** A large version of the clock, appropriate when a bigger viewport is available */
+ val largeClock: View
+
+ /** Callback to update the clock view to the current time */
+ fun onTimeTick()
+
+ /** Sets the level of the AOD transition */
+ fun setAodFraction(@FloatRange(from = 0.0, to = 1.0) fraction: Float)
+}
+
+/** Some data about a clock design */
+data class ClockMetadata(
+ val clockId: ClockId,
+ val name: String
+)
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
new file mode 100644
index 0000000..3245966
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.shared.clocks
+
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.shared.plugins.PluginManager
+import com.google.gson.Gson
+import javax.inject.Inject
+
+private val TAG = ClockRegistry::class.simpleName
+private val DEBUG = true
+const val DEFAULT_CLOCK_ID = "DEFAULT"
+
+typealias ClockChangeListener = () -> Unit
+
+/** ClockRegistry aggregates providers and plugins */
+open class ClockRegistry @Inject constructor(
+ val context: Context,
+ val pluginManager: PluginManager,
+ @Main val handler: Handler
+) {
+ private val gson = Gson()
+ private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
+ private val clockChangeListeners = mutableListOf<ClockChangeListener>()
+ private val settingObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
+ clockChangeListeners.forEach { it() }
+ }
+
+ private val pluginListener = object : PluginListener<ClockProviderPlugin> {
+ override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) {
+ val currentId = currentClockId
+ for (clock in plugin.getClocks()) {
+ val id = clock.clockId
+ val current = availableClocks[id]
+ if (current != null) {
+ Log.e(TAG, "Clock Id conflict: $id is registered by both " +
+ "${plugin::class.simpleName} and ${current.provider::class.simpleName}")
+ return
+ }
+
+ availableClocks[id] = ClockInfo(clock, plugin)
+
+ if (currentId == id) {
+ if (DEBUG) {
+ Log.i(TAG, "Current clock ($currentId) was connected")
+ }
+ clockChangeListeners.forEach { it() }
+ }
+ }
+ }
+
+ override fun onPluginDisconnected(plugin: ClockProviderPlugin) {
+ val currentId = currentClockId
+ for (clock in plugin.getClocks()) {
+ availableClocks.remove(clock.clockId)
+
+ if (currentId == clock.clockId) {
+ Log.w(TAG, "Current clock ($currentId) was disconnected")
+ clockChangeListeners.forEach { it() }
+ }
+ }
+ }
+ }
+
+ open var currentClockId: ClockId
+ get() {
+ val json = Settings.Secure.getString(context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE)
+ return gson.fromJson(json, ClockSetting::class.java).clockId
+ }
+ set(value) {
+ val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
+ Settings.Secure.putString(context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json)
+ }
+
+ init {
+ pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+ false,
+ settingObserver,
+ UserHandle.USER_ALL)
+ }
+
+ fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }
+
+ fun getClockThumbnail(clockId: ClockId): Drawable? =
+ availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
+
+ fun createExampleClock(clockId: ClockId): Clock? = createClock(clockId)
+
+ fun registerClockChangeListener(listener: ClockChangeListener) =
+ clockChangeListeners.add(listener)
+
+ fun unregisterClockChangeListener(listener: ClockChangeListener) =
+ clockChangeListeners.remove(listener)
+
+ fun createCurrentClock(): Clock {
+ val clockId = currentClockId
+ if (!clockId.isNullOrEmpty()) {
+ val clock = createClock(clockId)
+ if (clock != null) {
+ return clock
+ } else {
+ Log.e(TAG, "Clock $clockId not found; using default")
+ }
+ }
+
+ return createClock(DEFAULT_CLOCK_ID)!!
+ }
+
+ private fun createClock(clockId: ClockId): Clock? =
+ availableClocks[clockId]?.provider?.createClock(clockId)
+
+ private data class ClockInfo(
+ val metadata: ClockMetadata,
+ val provider: ClockProvider
+ )
+
+ private data class ClockSetting(
+ val clockId: ClockId,
+ val _applied_timestamp: Long
+ )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index 13f1db4a..0094820 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -65,14 +65,6 @@
}
}
- public void hideCurrentInputMethod() {
- try {
- mAnimationController.hideCurrentInputMethod();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to set hide input method", e);
- }
- }
-
/**
* Sets the final surface transaction on a Task. This is used by Launcher to notify the system
* that animating Activity to PiP has completed and the associated task surface should be
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index ff2a7a1..609846e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -369,10 +369,6 @@
if (mWrapped != null) mWrapped.setAnimationTargetsBehindSystemBars(behindSystemBars);
}
- @Override public void hideCurrentInputMethod() {
- mWrapped.hideCurrentInputMethod();
- }
-
@Override public void setFinishTaskTransaction(int taskId,
PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
mPipTransaction = finishTransaction;
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 013cdac..9a0bfc1 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -53,8 +53,11 @@
/**
* Manages custom clock faces for AOD and lock screen.
+ *
+ * @deprecated Migrate to ClockRegistry
*/
@SysUISingleton
+@Deprecated
public final class ClockManager {
private static final String TAG = "ClockOptsProvider";
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 08096b0..e9ca0fd 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -96,16 +96,11 @@
.setSplitScreen(mWMComponent.getSplitScreen())
.setOneHanded(mWMComponent.getOneHanded())
.setBubbles(mWMComponent.getBubbles())
- .setHideDisplayCutout(mWMComponent.getHideDisplayCutout())
- .setShellCommandHandler(mWMComponent.getShellCommandHandler())
.setTaskViewFactory(mWMComponent.getTaskViewFactory())
.setTransitions(mWMComponent.getTransitions())
.setStartingSurface(mWMComponent.getStartingSurface())
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
- .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
- .setCompatUI(mWMComponent.getCompatUI())
- .setDragAndDrop(mWMComponent.getDragAndDrop())
.setBackAnimation(mWMComponent.getBackAnimation());
} else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
@@ -116,16 +111,11 @@
.setSplitScreen(Optional.ofNullable(null))
.setOneHanded(Optional.ofNullable(null))
.setBubbles(Optional.ofNullable(null))
- .setHideDisplayCutout(Optional.ofNullable(null))
- .setShellCommandHandler(Optional.ofNullable(null))
.setTaskViewFactory(Optional.ofNullable(null))
.setTransitions(new ShellTransitions() {})
.setDisplayAreaHelper(Optional.ofNullable(null))
.setStartingSurface(Optional.ofNullable(null))
- .setTaskSurfaceHelper(Optional.ofNullable(null))
.setRecentTasks(Optional.ofNullable(null))
- .setCompatUI(Optional.ofNullable(null))
- .setDragAndDrop(Optional.ofNullable(null))
.setBackAnimation(Optional.ofNullable(null));
}
mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index fb502e5..cf50f7f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -609,7 +609,7 @@
@NonNull SystemUIDialogManager dialogManager,
@NonNull LatencyTracker latencyTracker,
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
- @NonNull Optional<AlternateUdfpsTouchProvider> aternateTouchProvider,
+ @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
@BiometricsBackground Executor biometricsExecutor) {
mContext = context;
mExecution = execution;
@@ -639,7 +639,7 @@
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mLatencyTracker = latencyTracker;
mActivityLaunchAnimator = activityLaunchAnimator;
- mAlternateTouchProvider = aternateTouchProvider.orElse(null);
+ mAlternateTouchProvider = alternateTouchProvider.orElse(null);
mBiometricExecutor = biometricsExecutor;
mOrientationListener = new BiometricDisplayListener(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index bed553e..50ce9d4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -20,13 +20,11 @@
import android.app.backup.BackupManager
import android.content.BroadcastReceiver
import android.content.ComponentName
-import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.database.ContentObserver
import android.net.Uri
-import android.os.Environment
import android.os.UserHandle
import android.service.controls.Control
import android.service.controls.actions.ControlAction
@@ -43,6 +41,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_SEEDING_COMPLETED
@@ -61,6 +60,7 @@
private val bindingController: ControlsBindingController,
private val listingController: ControlsListingController,
private val broadcastDispatcher: BroadcastDispatcher,
+ private val userFileManager: UserFileManager,
optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
dumpManager: DumpManager,
userTracker: UserTracker
@@ -84,15 +84,12 @@
override val currentUserId
get() = currentUser.identifier
- private val contentResolver: ContentResolver
- get() = context.contentResolver
-
private val persistenceWrapper: ControlsFavoritePersistenceWrapper
@VisibleForTesting
internal var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper
init {
- userStructure = UserStructure(context, currentUser)
+ userStructure = UserStructure(context, currentUser, userFileManager)
persistenceWrapper = optionalWrapper.orElseGet {
ControlsFavoritePersistenceWrapper(
@@ -111,7 +108,7 @@
private fun setValuesForUser(newUser: UserHandle) {
Log.d(TAG, "Changing to user: $newUser")
currentUser = newUser
- userStructure = UserStructure(context, currentUser)
+ userStructure = UserStructure(context, currentUser, userFileManager)
persistenceWrapper.changeFileAndBackupManager(
userStructure.file,
BackupManager(userStructure.userContext)
@@ -187,8 +184,11 @@
// When a component is uninstalled, allow seeding to happen again if the user
// reinstalls the app
- val prefs = userStructure.userContext.getSharedPreferences(
- PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
+ val prefs = userFileManager.getSharedPreferences(
+ PREFS_CONTROLS_FILE,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ )
val completedSeedingPackageSet = prefs.getStringSet(
PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
val servicePackageSet = serviceInfoSet.map { it.packageName }
@@ -575,18 +575,12 @@
}
}
-class UserStructure(context: Context, user: UserHandle) {
+class UserStructure(context: Context, user: UserHandle, userFileManager: UserFileManager) {
val userContext = context.createContextAsUser(user, 0)
-
- val file = Environment.buildPath(
- userContext.filesDir,
- ControlsFavoritePersistenceWrapper.FILE_NAME
- )
-
- val auxiliaryFile = Environment.buildPath(
- userContext.filesDir,
- AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME
- )
+ val file = userFileManager.getFile(ControlsFavoritePersistenceWrapper.FILE_NAME,
+ user.identifier)
+ val auxiliaryFile = userFileManager.getFile(AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME,
+ user.identifier)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index adeafd5..029cabb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -37,21 +37,16 @@
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;
-import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.sysui.ShellInterface;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
import com.android.wm.shell.transition.ShellTransitions;
import java.util.Map;
@@ -100,12 +95,6 @@
Builder setTaskViewFactory(Optional<TaskViewFactory> t);
@BindsInstance
- Builder setHideDisplayCutout(Optional<HideDisplayCutout> h);
-
- @BindsInstance
- Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump);
-
- @BindsInstance
Builder setTransitions(ShellTransitions t);
@BindsInstance
@@ -115,18 +104,9 @@
Builder setDisplayAreaHelper(Optional<DisplayAreaHelper> h);
@BindsInstance
- Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t);
-
- @BindsInstance
Builder setRecentTasks(Optional<RecentTasks> r);
@BindsInstance
- Builder setCompatUI(Optional<CompatUI> s);
-
- @BindsInstance
- Builder setDragAndDrop(Optional<DragAndDrop> d);
-
- @BindsInstance
Builder setBackAnimation(Optional<BackAnimation> b);
SysUIComponent build();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b6003e9..78a45f9 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -23,26 +23,22 @@
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;
import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.dagger.TvWMShellModule;
import com.android.wm.shell.dagger.WMShellModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.sysui.ShellInterface;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
import com.android.wm.shell.transition.ShellTransitions;
import java.util.Optional;
@@ -79,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();
@@ -104,9 +107,6 @@
Optional<Bubbles> getBubbles();
@WMSingleton
- Optional<HideDisplayCutout> getHideDisplayCutout();
-
- @WMSingleton
Optional<TaskViewFactory> getTaskViewFactory();
@WMSingleton
@@ -119,17 +119,8 @@
Optional<DisplayAreaHelper> getDisplayAreaHelper();
@WMSingleton
- Optional<TaskSurfaceHelper> getTaskSurfaceHelper();
-
- @WMSingleton
Optional<RecentTasks> getRecentTasks();
@WMSingleton
- Optional<CompatUI> getCompatUI();
-
- @WMSingleton
- Optional<DragAndDrop> getDragAndDrop();
-
- @WMSingleton
Optional<BackAnimation> getBackAnimation();
}
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..a566984 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
@@ -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/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index aeff2d4..012d766 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -682,9 +682,22 @@
Drawable artwork;
boolean isArtworkBound;
Icon artworkIcon = data.getArtwork();
+ WallpaperColors wallpaperColors = null;
if (artworkIcon != null) {
- WallpaperColors wallpaperColors = WallpaperColors
- .fromBitmap(artworkIcon.getBitmap());
+ if (artworkIcon.getType() == Icon.TYPE_BITMAP
+ || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+ // Avoids extra processing if this is already a valid bitmap
+ wallpaperColors = WallpaperColors
+ .fromBitmap(artworkIcon.getBitmap());
+ } else {
+ Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
+ if (artworkDrawable != null) {
+ wallpaperColors = WallpaperColors
+ .fromDrawable(artworkIcon.loadDrawable(mContext));
+ }
+ }
+ }
+ if (wallpaperColors != null) {
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
Drawable albumArt = getScaledBackground(artworkIcon, width, height);
GradientDrawable gradient = (GradientDrawable) mContext
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
index e1b97a4..20c6c556 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -21,6 +21,7 @@
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
@@ -34,6 +35,7 @@
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -43,6 +45,7 @@
import com.android.wm.shell.bubbles.Bubble;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -58,6 +61,7 @@
private boolean mIsForTesting;
private IStatusBarService mIStatusBarService;
private CommandQueue mCommandQueue;
+ private Executor mBgExecutor;
private Bubble mBubble;
private NotificationEntry mEntryToBubble;
@@ -67,7 +71,8 @@
CommonNotifCollection commonNotifCollection,
Optional<BubblesManager> bubblesManagerOptional,
UserManager userManager,
- CommandQueue commandQueue
+ CommandQueue commandQueue,
+ @Background Executor bgExecutor
) {
super();
mVisibilityProvider = visibilityProvider;
@@ -91,6 +96,7 @@
mCommandQueue.removeCallback(this);
}
});
+ mBgExecutor = bgExecutor;
}
@Override
@@ -172,34 +178,36 @@
return;
}
- try {
- if (mIStatusBarService == null || mCommonNotifCollection == null) {
- if (DEBUG) {
- Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey);
- }
- return;
+ if (mIStatusBarService == null || mCommonNotifCollection == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey);
}
-
- NotificationEntry entry = mCommonNotifCollection.getEntry(notifKey);
- if (entry == null || entry.getRanking() == null) {
- if (DEBUG) {
- Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking"
- + " is null, key: " + notifKey);
- }
- return;
- }
-
- NotificationVisibility notifVisibility = mVisibilityProvider.obtain(entry, true);
- int rank = notifVisibility.rank;
-
- if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank);
- mIStatusBarService.onNotificationClear(
- packageName, userHandle.getIdentifier(), notifKey,
- NotificationStats.DISMISSAL_OTHER,
- NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility);
- } catch (Exception e) {
- Log.e(TAG, "Exception cancelling notification:" + e);
+ return;
}
+
+ NotificationEntry entry = mCommonNotifCollection.getEntry(notifKey);
+ if (entry == null || entry.getRanking() == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking"
+ + " is null, key: " + notifKey);
+ }
+ return;
+ }
+
+ NotificationVisibility notifVisibility = mVisibilityProvider.obtain(entry, true);
+ int rank = notifVisibility.rank;
+
+ if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank);
+ mBgExecutor.execute(() -> {
+ try {
+ mIStatusBarService.onNotificationClear(
+ packageName, userHandle.getIdentifier(), notifKey,
+ NotificationStats.DISMISSAL_OTHER,
+ NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception cancelling notification:" + e);
+ }
+ });
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 5bb3413..a837cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -29,6 +29,7 @@
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -40,6 +41,8 @@
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.LongRunning;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -51,9 +54,10 @@
/**
* A service which records the device screen and optionally microphone input.
*/
-public class RecordingService extends Service implements MediaRecorder.OnInfoListener {
+public class RecordingService extends Service implements ScreenMediaRecorderListener {
public static final int REQUEST_CODE = 2;
+ private static final int USER_ID_NOT_SPECIFIED = -1;
private static final int NOTIFICATION_RECORDING_ID = 4274;
private static final int NOTIFICATION_PROCESSING_ID = 4275;
private static final int NOTIFICATION_VIEW_ID = 4273;
@@ -73,6 +77,7 @@
private final RecordingController mController;
private final KeyguardDismissUtil mKeyguardDismissUtil;
+ private final Handler mMainHandler;
private ScreenRecordingAudioSource mAudioSource;
private boolean mShowTaps;
private boolean mOriginalShowTaps;
@@ -84,10 +89,12 @@
@Inject
public RecordingService(RecordingController controller, @LongRunning Executor executor,
- UiEventLogger uiEventLogger, NotificationManager notificationManager,
+ @Main Handler handler, UiEventLogger uiEventLogger,
+ NotificationManager notificationManager,
UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
mController = controller;
mLongExecutor = executor;
+ mMainHandler = handler;
mUiEventLogger = uiEventLogger;
mNotificationManager = notificationManager;
mUserContextTracker = userContextTracker;
@@ -138,6 +145,7 @@
mRecorder = new ScreenMediaRecorder(
mUserContextTracker.getUserContext(),
+ mMainHandler,
currentUserId,
mAudioSource,
this
@@ -166,14 +174,8 @@
}
// Check user ID - we may be getting a stop intent after user switch, in which case
// we want to post the notifications for that user, which is NOT current user
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userId == -1) {
- userId = mUserContextTracker.getUserContext().getUserId();
- }
- Log.d(TAG, "notifying for user " + userId);
- stopRecording(userId);
- mNotificationManager.cancel(NOTIFICATION_RECORDING_ID);
- stopSelf();
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_ID_NOT_SPECIFIED);
+ stopService(userId);
break;
case ACTION_SHARE:
@@ -378,15 +380,39 @@
return builder.build();
}
- private void stopRecording(int userId) {
+ private void stopService() {
+ stopService(USER_ID_NOT_SPECIFIED);
+ }
+
+ private void stopService(int userId) {
+ if (userId == USER_ID_NOT_SPECIFIED) {
+ userId = mUserContextTracker.getUserContext().getUserId();
+ }
+ Log.d(TAG, "notifying for user " + userId);
setTapsVisible(mOriginalShowTaps);
if (getRecorder() != null) {
- getRecorder().end();
- saveRecording(userId);
+ try {
+ getRecorder().end();
+ saveRecording(userId);
+ } catch (RuntimeException exception) {
+ // RuntimeException could happen if the recording stopped immediately after starting
+ // let's release the recorder and delete all temporary files in this case
+ getRecorder().release();
+ showErrorToast(R.string.screenrecord_start_error);
+ Log.e(TAG, "stopRecording called, but there was an error when ending"
+ + "recording");
+ exception.printStackTrace();
+ } catch (Throwable throwable) {
+ // Something unexpected happen, SystemUI will crash but let's delete
+ // the temporary files anyway
+ getRecorder().release();
+ throw new RuntimeException(throwable);
+ }
} else {
Log.e(TAG, "stopRecording called, but recorder was null");
}
updateState(false);
+ stopSelf();
}
private void saveRecording(int userId) {
@@ -446,4 +472,12 @@
Log.d(TAG, "Media recorder info: " + what);
onStartCommand(getStopIntent(this), 0, 0);
}
+
+ @Override
+ public void onStopped() {
+ if (mController.isRecording()) {
+ Log.d(TAG, "Stopping recording because the system requested the stop");
+ stopService();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 2133cf6..d098b4b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -40,6 +40,7 @@
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.net.Uri;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -51,16 +52,19 @@
import android.view.WindowManager;
import java.io.File;
+import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
/**
* Recording screen and mic/internal audio
*/
-public class ScreenMediaRecorder {
+public class ScreenMediaRecorder extends MediaProjection.Callback {
private static final int TOTAL_NUM_TRACKS = 1;
private static final int VIDEO_FRAME_RATE = 30;
private static final int VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO = 6;
@@ -81,14 +85,16 @@
private ScreenRecordingMuxer mMuxer;
private ScreenInternalAudioRecorder mAudio;
private ScreenRecordingAudioSource mAudioSource;
+ private final Handler mHandler;
private Context mContext;
- MediaRecorder.OnInfoListener mListener;
+ ScreenMediaRecorderListener mListener;
- public ScreenMediaRecorder(Context context,
+ public ScreenMediaRecorder(Context context, Handler handler,
int user, ScreenRecordingAudioSource audioSource,
- MediaRecorder.OnInfoListener listener) {
+ ScreenMediaRecorderListener listener) {
mContext = context;
+ mHandler = handler;
mUser = user;
mListener = listener;
mAudioSource = audioSource;
@@ -105,6 +111,7 @@
IBinder projection = proj.asBinder();
mMediaProjection = new MediaProjection(mContext,
IMediaProjection.Stub.asInterface(projection));
+ mMediaProjection.registerCallback(this, mHandler);
File cacheDir = mContext.getCacheDir();
cacheDir.mkdirs();
@@ -162,10 +169,15 @@
metrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mInputSurface,
- null,
- null);
+ new VirtualDisplay.Callback() {
+ @Override
+ public void onStopped() {
+ onStop();
+ }
+ },
+ mHandler);
- mMediaRecorder.setOnInfoListener(mListener);
+ mMediaRecorder.setOnInfoListener((mr, what, extra) -> mListener.onInfo(mr, what, extra));
if (mAudioSource == INTERNAL ||
mAudioSource == MIC_AND_INTERNAL) {
mTempAudioFile = File.createTempFile("temp", ".aac",
@@ -259,21 +271,34 @@
}
/**
- * End screen recording
+ * End screen recording, throws an exception if stopping recording failed
*/
- void end() {
- mMediaRecorder.stop();
- mMediaRecorder.release();
- mInputSurface.release();
- mVirtualDisplay.release();
- mMediaProjection.stop();
+ void end() throws IOException {
+ Closer closer = new Closer();
+
+ // MediaRecorder might throw RuntimeException if stopped immediately after starting
+ // We should remove the recording in this case as it will be invalid
+ closer.register(mMediaRecorder::stop);
+ closer.register(mMediaRecorder::release);
+ closer.register(mInputSurface::release);
+ closer.register(mVirtualDisplay::release);
+ closer.register(mMediaProjection::stop);
+ closer.register(this::stopInternalAudioRecording);
+
+ closer.close();
+
mMediaRecorder = null;
mMediaProjection = null;
- stopInternalAudioRecording();
Log.d(TAG, "end recording");
}
+ @Override
+ public void onStop() {
+ Log.d(TAG, "The system notified about stopping the projection");
+ mListener.onStopped();
+ }
+
private void stopInternalAudioRecording() {
if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
mAudio.end();
@@ -337,6 +362,18 @@
}
/**
+ * Release the resources without saving the data
+ */
+ protected void release() {
+ if (mTempVideoFile != null) {
+ mTempVideoFile.delete();
+ }
+ if (mTempAudioFile != null) {
+ mTempAudioFile.delete();
+ }
+ }
+
+ /**
* Object representing the recording
*/
public class SavedRecording {
@@ -362,4 +399,66 @@
return mThumbnailBitmap;
}
}
+
+ interface ScreenMediaRecorderListener {
+ /**
+ * Called to indicate an info or a warning during recording.
+ * See {@link MediaRecorder.OnInfoListener} for the full description.
+ */
+ void onInfo(MediaRecorder mr, int what, int extra);
+
+ /**
+ * Called when the recording stopped by the system.
+ * For example, this might happen when doing partial screen sharing of an app
+ * and the app that is being captured is closed.
+ */
+ void onStopped();
+ }
+
+ /**
+ * Allows to register multiple {@link Closeable} objects and close them all by calling
+ * {@link Closer#close}. If there is an exception thrown during closing of one
+ * of the registered closeables it will continue trying closing the rest closeables.
+ * If there are one or more exceptions thrown they will be re-thrown at the end.
+ * In case of multiple exceptions only the first one will be thrown and all the rest
+ * will be printed.
+ */
+ private static class Closer implements Closeable {
+ private final List<Closeable> mCloseables = new ArrayList<>();
+
+ void register(Closeable closeable) {
+ mCloseables.add(closeable);
+ }
+
+ @Override
+ public void close() throws IOException {
+ Throwable throwable = null;
+
+ for (int i = 0; i < mCloseables.size(); i++) {
+ Closeable closeable = mCloseables.get(i);
+
+ try {
+ closeable.close();
+ } catch (Throwable e) {
+ if (throwable == null) {
+ throwable = e;
+ } else {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ if (throwable != null) {
+ if (throwable instanceof IOException) {
+ throw (IOException) throwable;
+ }
+
+ if (throwable instanceof RuntimeException) {
+ throw (RuntimeException) throwable;
+ }
+
+ throw (Error) throwable;
+ }
+ }
+ }
}
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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index bab92ba..61d1963 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -189,7 +189,6 @@
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;
@@ -1038,6 +1037,7 @@
}
mTapAgainViewController.init();
+ mLargeScreenShadeHeaderController.init();
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
controller.setup(mNotificationContainerParent));
@@ -1143,7 +1143,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();
@@ -2417,7 +2417,7 @@
: getExpandedFraction();
mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
- mLargeScreenShadeHeaderController.setShadeExpanded(mQsVisible);
+ mLargeScreenShadeHeaderController.setQsVisible(mQsVisible);
}
private void onStackYChanged(boolean shouldAnimate) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 1c4911d..58acfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -7,6 +7,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
@@ -27,7 +29,8 @@
private val context: Context,
private val splitShadeOverScrollerFactory: SplitShadeOverScroller.Factory,
private val noOpOverScroller: NoOpOverScroller,
- private val scrimShadeTransitionController: ScrimShadeTransitionController
+ private val scrimShadeTransitionController: ScrimShadeTransitionController,
+ private val statusBarStateController: SysuiStatusBarStateController,
) {
lateinit var notificationPanelViewController: NotificationPanelViewController
@@ -43,7 +46,7 @@
}
private val shadeOverScroller: ShadeOverScroller
get() =
- if (inSplitShade && propertiesInitialized()) {
+ if (inSplitShade && isScreenUnlocked() && propertiesInitialized()) {
splitShadeOverScroller
} else {
noOpOverScroller
@@ -90,6 +93,7 @@
"""
ShadeTransitionController:
inSplitShade: $inSplitShade
+ isScreenUnlocked: ${isScreenUnlocked()}
currentPanelState: ${currentPanelState?.panelStateToString()}
lastPanelExpansionChangeEvent: $lastPanelExpansionChangeEvent
qs.isInitialized: ${this::qs.isInitialized}
@@ -97,4 +101,7 @@
nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized}
""".trimIndent())
}
+
+ private fun isScreenUnlocked() =
+ statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
}
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/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index c86bf93..852d5f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -41,6 +41,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
@@ -73,6 +74,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Executor;
import dagger.Lazy;
@@ -113,6 +115,7 @@
private final IStatusBarService mStatusBarService;
private final NotifLiveDataStoreImpl mNotifLiveDataStore;
private final DumpManager mDumpManager;
+ private final Executor mBgExecutor;
private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
private final Set<NotificationEntry> mReadOnlyAllNotifications =
@@ -159,7 +162,8 @@
LeakDetector leakDetector,
IStatusBarService statusBarService,
NotifLiveDataStoreImpl notifLiveDataStore,
- DumpManager dumpManager
+ DumpManager dumpManager,
+ @Background Executor bgExecutor
) {
mLogger = logger;
mGroupManager = groupManager;
@@ -170,6 +174,7 @@
mStatusBarService = statusBarService;
mNotifLiveDataStore = notifLiveDataStore;
mDumpManager = dumpManager;
+ mBgExecutor = bgExecutor;
}
/** Once called, the NEM will start processing notification events from system server. */
@@ -566,17 +571,19 @@
private void sendNotificationRemovalToServer(
StatusBarNotification notification,
DismissedByUserStats dismissedByUserStats) {
- try {
- mStatusBarService.onNotificationClear(
- notification.getPackageName(),
- notification.getUser().getIdentifier(),
- notification.getKey(),
- dismissedByUserStats.dismissalSurface,
- dismissedByUserStats.dismissalSentiment,
- dismissedByUserStats.notificationVisibility);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
+ mBgExecutor.execute(() -> {
+ try {
+ mStatusBarService.onNotificationClear(
+ notification.getPackageName(),
+ notification.getUser().getIdentifier(),
+ notification.getKey(),
+ dismissedByUserStats.dismissalSurface,
+ dismissedByUserStats.dismissalSentiment,
+ dismissedByUserStats.notificationVisibility);
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+ });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 2c85fdd..351a4be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -70,6 +70,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.LogBufferEulogizer;
@@ -112,6 +113,7 @@
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
@@ -146,6 +148,7 @@
private final NotifPipelineFlags mNotifPipelineFlags;
private final NotifCollectionLogger mLogger;
private final Handler mMainHandler;
+ private final Executor mBgExecutor;
private final LogBufferEulogizer mEulogizer;
private final DumpManager mDumpManager;
@@ -174,6 +177,7 @@
NotifPipelineFlags notifPipelineFlags,
NotifCollectionLogger logger,
@Main Handler mainHandler,
+ @Background Executor bgExecutor,
LogBufferEulogizer logBufferEulogizer,
DumpManager dumpManager) {
mStatusBarService = statusBarService;
@@ -181,6 +185,7 @@
mNotifPipelineFlags = notifPipelineFlags;
mLogger = logger;
mMainHandler = mainHandler;
+ mBgExecutor = bgExecutor;
mEulogizer = logBufferEulogizer;
mDumpManager = dumpManager;
}
@@ -294,18 +299,20 @@
entriesToLocallyDismiss.add(entry);
if (!isCanceled(entry)) {
// send message to system server if this notification hasn't already been cancelled
- try {
- mStatusBarService.onNotificationClear(
- entry.getSbn().getPackageName(),
- entry.getSbn().getUser().getIdentifier(),
- entry.getSbn().getKey(),
- stats.dismissalSurface,
- stats.dismissalSentiment,
- stats.notificationVisibility);
- } catch (RemoteException e) {
- // system process is dead if we're here.
- mLogger.logRemoteExceptionOnNotificationClear(entry, e);
- }
+ mBgExecutor.execute(() -> {
+ try {
+ mStatusBarService.onNotificationClear(
+ entry.getSbn().getPackageName(),
+ entry.getSbn().getUser().getIdentifier(),
+ entry.getSbn().getKey(),
+ stats.dismissalSurface,
+ stats.dismissalSentiment,
+ stats.notificationVisibility);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ mLogger.logRemoteExceptionOnNotificationClear(entry, e);
+ }
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 4956a276..bf08fc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -128,7 +128,8 @@
LeakDetector leakDetector,
IStatusBarService statusBarService,
NotifLiveDataStoreImpl notifLiveDataStore,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Background Executor bgExecutor) {
return new NotificationEntryManager(
logger,
groupManager,
@@ -138,7 +139,8 @@
leakDetector,
statusBarService,
notifLiveDataStore,
- dumpManager);
+ dumpManager,
+ bgExecutor);
}
/** Provides an instance of {@link NotificationGutsManager} */
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 fff7b6a..2d6d846 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1709,7 +1709,7 @@
// Wrap the animation controller to dismiss the shade and set
// mIsLaunchingActivityOverLockscreen during the animation.
ActivityLaunchAnimator.Controller delegate = wrapAnimationController(
- animationController, dismissShade);
+ animationController, dismissShade, /* isLaunchForActivity= */ true);
controller = new DelegateLaunchAnimatorController(delegate) {
@Override
public void onIntentStarted(boolean willAnimate) {
@@ -2457,7 +2457,7 @@
true /* isActivityIntent */);
ActivityLaunchAnimator.Controller animController =
animationController != null ? wrapAnimationController(animationController,
- dismissShade) : null;
+ dismissShade, /* isLaunchForActivity= */ true) : null;
// If we animate, we will dismiss the shade only once the animation is done. This is taken
// care of by the StatusBarLaunchAnimationController.
@@ -2535,9 +2535,25 @@
willLaunchResolverActivity, deferred /* deferred */, animate);
}
+ /**
+ * Return a {@link ActivityLaunchAnimator.Controller} wrapping {@code animationController} so
+ * that:
+ * - if it launches in the notification shade window and {@code dismissShade} is true, then
+ * the shade will be instantly dismissed at the end of the animation.
+ * - if it launches in status bar window, it will make the status bar window match the device
+ * size during the animation (that way, the animation won't be clipped by the status bar
+ * size).
+ *
+ * @param animationController the controller that is wrapped and will drive the main animation.
+ * @param dismissShade whether the notification shade will be dismissed at the end of the
+ * animation. This is ignored if {@code animationController} is not
+ * animating in the shade window.
+ * @param isLaunchForActivity whether the launch is for an activity.
+ */
@Nullable
private ActivityLaunchAnimator.Controller wrapAnimationController(
- ActivityLaunchAnimator.Controller animationController, boolean dismissShade) {
+ ActivityLaunchAnimator.Controller animationController, boolean dismissShade,
+ boolean isLaunchForActivity) {
View rootView = animationController.getLaunchContainer().getRootView();
Optional<ActivityLaunchAnimator.Controller> controllerFromStatusBar =
@@ -2551,7 +2567,7 @@
// If the view is not in the status bar, then we are animating a view in the shade.
// We have to make sure that we collapse it when the animation ends or is cancelled.
return new StatusBarLaunchAnimatorController(animationController, this,
- true /* isLaunchForActivity */);
+ isLaunchForActivity);
}
return animationController;
@@ -4083,8 +4099,9 @@
// We wrap animationCallback with a StatusBarLaunchAnimatorController so that the
// shade is collapsed after the animation (or when it is cancelled, aborted, etc).
ActivityLaunchAnimator.Controller controller =
- animationController != null ? new StatusBarLaunchAnimatorController(
- animationController, this, intent.isActivity()) : null;
+ animationController != null ? wrapAnimationController(
+ animationController, /* dismissShade= */ true, intent.isActivity())
+ : null;
mActivityLaunchAnimator.startPendingIntentWithAnimation(
controller, animate, intent.getCreatorPackage(),
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/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 2dc3261..a2140c6ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License
*/
-
package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY;
@@ -31,6 +30,7 @@
import android.util.Property;
import android.view.ContextThemeWrapper;
import android.view.View;
+import android.view.ViewGroup;
import android.view.animation.Interpolator;
import androidx.annotation.VisibleForTesting;
@@ -40,7 +40,6 @@
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -54,7 +53,7 @@
* A container for notification icons. It handles overflowing icons properly and positions them
* correctly on the screen.
*/
-public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
+public class NotificationIconContainer extends ViewGroup {
/**
* A float value indicating how much before the overflow start the icons should transform into
* a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts
@@ -232,6 +231,31 @@
}
@Override
+ public boolean hasOverlappingRendering() {
+ // Does the same as "AlphaOptimizedFrameLayout".
+ return false;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int childCount = getChildCount();
+ final int maxVisibleIcons = getMaxVisibleIcons(childCount);
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+ final int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
+ int totalWidth = (int) (getActualPaddingStart() + getActualPaddingEnd());
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ measureChild(child, childWidthSpec, heightMeasureSpec);
+ if (i <= maxVisibleIcons) {
+ totalWidth += child.getMeasuredWidth();
+ }
+ }
+ final int measuredWidth = resolveSize(totalWidth, widthMeasureSpec);
+ final int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+
+ @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
float centerY = getHeight() / 2.0f;
// we layout all our children on the left at the top
@@ -408,8 +432,7 @@
float translationX = getActualPaddingStart();
int firstOverflowIndex = -1;
int childCount = getChildCount();
- int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD :
- mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
+ int maxVisibleIcons = getMaxVisibleIcons(childCount);
float layoutEnd = getLayoutEnd();
mVisualOverflowStart = 0;
mFirstVisibleIconState = null;
@@ -493,6 +516,11 @@
}
}
+ private int getMaxVisibleIcons(int childCount) {
+ return mOnLockScreen ? MAX_ICONS_ON_AOD :
+ mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
+ }
+
private float getLayoutEnd() {
return getActualWidth() - getActualPaddingEnd();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 2052ee6..15c6dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -31,7 +31,7 @@
private final float mIconAlphaWhenOpaque;
- private View mLeftSide, mStatusIcons, mBattery;
+ private View mStartSide, mStatusIcons, mBattery;
private Animator mCurrentAnimation;
/**
@@ -41,7 +41,7 @@
super(backgroundView, R.drawable.status_background);
final Resources res = statusBarView.getContext().getResources();
mIconAlphaWhenOpaque = res.getFraction(R.dimen.status_bar_icon_drawing_alpha, 1, 1);
- mLeftSide = statusBarView.findViewById(R.id.status_bar_left_side);
+ mStartSide = statusBarView.findViewById(R.id.status_bar_start_side_except_heads_up);
mStatusIcons = statusBarView.findViewById(R.id.statusIcons);
mBattery = statusBarView.findViewById(R.id.battery);
applyModeBackground(-1, getMode(), false /*animate*/);
@@ -75,7 +75,7 @@
}
private void applyMode(int mode, boolean animate) {
- if (mLeftSide == null) return; // pre-init
+ if (mStartSide == null) return; // pre-init
float newAlpha = getNonBatteryClockAlphaFor(mode);
float newAlphaBC = getBatteryClockAlpha(mode);
if (mCurrentAnimation != null) {
@@ -84,7 +84,7 @@
if (animate) {
AnimatorSet anims = new AnimatorSet();
anims.playTogether(
- animateTransitionTo(mLeftSide, newAlpha),
+ animateTransitionTo(mStartSide, newAlpha),
animateTransitionTo(mStatusIcons, newAlpha),
animateTransitionTo(mBattery, newAlphaBC)
);
@@ -94,7 +94,7 @@
anims.start();
mCurrentAnimation = anims;
} else {
- mLeftSide.setAlpha(newAlpha);
+ mStartSide.setAlpha(newAlpha);
mStatusIcons.setAlpha(newAlpha);
mBattery.setAlpha(newAlphaBC);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 9da2ef73..f9c4c8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -21,7 +21,6 @@
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
-
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
@@ -32,9 +31,7 @@
import com.android.systemui.util.ViewController
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.view.ViewUtil
-
import java.util.Optional
-
import javax.inject.Inject
import javax.inject.Named
@@ -58,8 +55,8 @@
override fun onViewAttached() {
if (moveFromCenterAnimationController == null) return
- val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
- val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
+ val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up)
+ val systemIconArea: ViewGroup = mView.findViewById(R.id.status_bar_end_side_content)
val viewsToAnimate = arrayOf(
statusBarLeftSide,
@@ -126,11 +123,11 @@
class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
override fun getViewCenter(view: View, outPoint: Point) =
when (view.id) {
- R.id.status_bar_left_side -> {
+ R.id.status_bar_start_side_except_heads_up -> {
// items aligned to the start, return start center point
getViewEdgeCenter(view, outPoint, isStart = true)
}
- R.id.system_icon_area -> {
+ R.id.status_bar_end_side_content -> {
// items aligned to the end, return end center point
getViewEdgeCenter(view, outPoint, isStart = false)
}
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 0e7da81..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";
@@ -102,7 +110,7 @@
private final KeyguardStateController mKeyguardStateController;
private final NotificationPanelViewController mNotificationPanelViewController;
private final NetworkController mNetworkController;
- private LinearLayout mSystemIconArea;
+ private LinearLayout mEndSideContent;
private View mClockView;
private View mOngoingCallChip;
private View mNotificationIconAreaInner;
@@ -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);
@@ -232,16 +251,16 @@
mDarkIconManager.setShouldLog(true);
updateBlockedIcons();
mStatusBarIconController.addIconGroup(mDarkIconManager);
- mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
+ mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
mClockView = mStatusBar.findViewById(R.id.clock);
mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
- showSystemIconArea(false);
+ showEndSideContent(false);
showClock(false);
initEmergencyCryptkeeperText();
initOperatorName();
initNotificationIconArea();
mSystemEventAnimator =
- new StatusBarSystemEventAnimator(mSystemIconArea, getResources());
+ new StatusBarSystemEventAnimator(mEndSideContent, getResources());
mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
}
@@ -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. */
@@ -370,10 +396,10 @@
mDisabled2 = state2;
if ((diff1 & DISABLE_SYSTEM_INFO) != 0 || ((diff2 & DISABLE2_SYSTEM_ICONS) != 0)) {
if ((state1 & DISABLE_SYSTEM_INFO) != 0 || ((state2 & DISABLE2_SYSTEM_ICONS) != 0)) {
- hideSystemIconArea(animate);
+ hideEndSideContent(animate);
hideOperatorName(animate);
} else {
- showSystemIconArea(animate);
+ showEndSideContent(animate);
showOperatorName(animate);
}
}
@@ -474,15 +500,15 @@
return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer();
}
- private void hideSystemIconArea(boolean animate) {
- animateHide(mSystemIconArea, animate);
+ private void hideEndSideContent(boolean animate) {
+ animateHide(mEndSideContent, animate);
}
- private void showSystemIconArea(boolean animate) {
+ private void showEndSideContent(boolean animate) {
// Only show the system icon area if we are not currently animating
int state = mAnimationScheduler.getAnimationState();
if (state == IDLE || state == SHOWING_PERSISTENT_DOT) {
- animateShow(mSystemIconArea, animate);
+ animateShow(mEndSideContent, animate);
}
}
@@ -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/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index e08a9d0..708a8ab 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -34,7 +34,6 @@
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.UserInfo;
-import android.content.res.Configuration;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index fa2af56..12597e0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -54,10 +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.compatui.CompatUI;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -110,10 +106,6 @@
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
- private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
- private final Optional<ShellCommandHandler> mShellCommandHandler;
- private final Optional<CompatUI> mCompatUIOptional;
- private final Optional<DragAndDrop> mDragAndDropOptional;
private final CommandQueue mCommandQueue;
private final ConfigurationController mConfigurationController;
@@ -127,10 +119,7 @@
private final Executor mSysUiMainExecutor;
private boolean mIsSysUiStateValid;
- private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
- private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
- private KeyguardStateController.Callback mCompatUIKeyguardCallback;
private WakefulnessLifecycle.Observer mWakefulnessObserver;
@Inject
@@ -139,10 +128,6 @@
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
- Optional<HideDisplayCutout> hideDisplayCutoutOptional,
- Optional<ShellCommandHandler> shellCommandHandler,
- Optional<CompatUI> sizeCompatUIOptional,
- Optional<DragAndDrop> dragAndDropOptional,
CommandQueue commandQueue,
ConfigurationController configurationController,
KeyguardStateController keyguardStateController,
@@ -164,12 +149,8 @@
mPipOptional = pipOptional;
mSplitScreenOptional = splitScreenOptional;
mOneHandedOptional = oneHandedOptional;
- mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
mWakefulnessLifecycle = wakefulnessLifecycle;
mProtoTracer = protoTracer;
- mShellCommandHandler = shellCommandHandler;
- mCompatUIOptional = sizeCompatUIOptional;
- mDragAndDropOptional = dragAndDropOptional;
mUserInfoController = userInfoController;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -185,6 +166,22 @@
}
});
+ // Subscribe to keyguard changes
+ mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ mShell.onKeyguardVisibilityChanged(mKeyguardStateController.isShowing(),
+ mKeyguardStateController.isOccluded(),
+ mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind());
+ }
+ });
+ mKeyguardUpdateMonitor.registerCallback(new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onKeyguardDismissAnimationFinished() {
+ mShell.onKeyguardDismissAnimationFinished();
+ }
+ });
+
// TODO: Consider piping config change and other common calls to a shell component to
// delegate internally
mProtoTracer.add(this);
@@ -192,7 +189,6 @@
mPipOptional.ifPresent(this::initPip);
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
- mCompatUIOptional.ifPresent(this::initCompatUi);
}
@VisibleForTesting
@@ -204,20 +200,6 @@
}
});
- mPipKeyguardCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- pip.onKeyguardVisibilityChanged(showing,
- mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind());
- }
-
- @Override
- public void onKeyguardDismissAnimationFinished() {
- pip.onKeyguardDismissAnimationFinished();
- }
- };
- mKeyguardUpdateMonitor.registerCallback(mPipKeyguardCallback);
-
mSysUiState.addCallback(sysUiStateFlag -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
@@ -230,14 +212,6 @@
@VisibleForTesting
void initSplitScreen(SplitScreen splitScreen) {
- mSplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- splitScreen.onKeyguardVisibilityChanged(showing);
- }
- };
- mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
-
mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedWakingUp() {
@@ -283,14 +257,9 @@
}
});
+ // TODO: Either move into ShellInterface or register a receiver on the Shell side directly
mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- oneHanded.onKeyguardVisibilityChanged(showing);
- oneHanded.stopOneHanded();
- }
-
- @Override
public void onUserSwitchComplete(int userId) {
oneHanded.onUserSwitch(userId);
}
@@ -340,17 +309,6 @@
});
}
- @VisibleForTesting
- void initCompatUi(CompatUI sizeCompatUI) {
- mCompatUIKeyguardCallback = new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardShowingChanged() {
- sizeCompatUI.onKeyguardShowingChanged(mKeyguardStateController.isShowing());
- }
- };
- mKeyguardStateController.addCallback(mCompatUIKeyguardCallback);
- }
-
@Override
public void writeToProto(SystemUiTraceProto proto) {
if (proto.wmShell == null) {
@@ -363,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
@@ -372,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/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 3c7ea4f..c31fd82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -37,9 +37,14 @@
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.util.Optional
+import java.util.function.Consumer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -50,20 +55,20 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
-import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -85,6 +90,8 @@
private lateinit var listingController: ControlsListingController
@Mock(stubOnly = true)
private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var userFileManager: UserFileManager
@Captor
private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
@@ -153,6 +160,9 @@
canceller = DidRunRunnable()
`when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
+ `when`(userFileManager.getFile(anyString(), anyInt())).thenReturn(mock(File::class.java))
+ `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
+ .thenReturn(context.getSharedPreferences("test", Context.MODE_PRIVATE))
controller = ControlsControllerImpl(
wrapper,
@@ -161,6 +171,7 @@
bindingController,
listingController,
broadcastDispatcher,
+ userFileManager,
Optional.of(persistenceWrapper),
mock(DumpManager::class.java),
userTracker
@@ -217,6 +228,7 @@
bindingController,
listingController,
broadcastDispatcher,
+ userFileManager,
Optional.of(persistenceWrapper),
mock(DumpManager::class.java),
userTracker
@@ -911,6 +923,14 @@
assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT_2, TEST_STRUCTURE).isEmpty())
}
+
+ @Test
+ fun testUserStructure() {
+ val userStructure = UserStructure(context, context.user, userFileManager)
+ verify(userFileManager, times(2))
+ .getFile(ControlsFavoritePersistenceWrapper.FILE_NAME, context.user.identifier)
+ assertThat(userStructure.file).isNotNull()
+ }
}
private class DidRunRunnable() : Runnable {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index c13c30b..1785022 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -561,6 +561,19 @@
}
@Test
+ fun bindAlbumView_artUsesResource() {
+ val albumArt = Icon.createWithResource(context, R.drawable.ic_android)
+ val state = mediaData.copy(artwork = albumArt)
+
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(state, PACKAGE)
+ bgExecutor.runAllReady()
+ mainExecutor.runAllReady()
+
+ verify(albumView).setImageDrawable(any(Drawable::class.java))
+ }
+
+ @Test
fun bindAlbumView_setAfterExecutors() {
val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
index 99f21ad..c8ebd12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
@@ -48,6 +48,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubble;
@@ -106,6 +108,9 @@
private Intent mIntent;
+ private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -114,7 +119,8 @@
mNotifCollection,
Optional.of(mBubblesManager),
mUserManager,
- mCommandQueue
+ mCommandQueue,
+ mBgExecutor
);
verify(mCommandQueue, times(1)).addCallback(mCallbacksCaptor.capture());
@@ -193,6 +199,7 @@
// Ensure callback removed
verify(mCommandQueue).removeCallback(any());
// Clear the notification for bubbles.
+ FakeExecutor.exhaustExecutors(mBgExecutor);
verify(mIStatusBarService, times(1)).onNotificationClear(any(),
anyInt(), any(), anyInt(), anyInt(), mNotificationVisibilityCaptor.capture());
// Do not select the bubble.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index 91cafea..b05d9a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -16,18 +16,21 @@
package com.android.systemui.screenrecord;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Intent;
+import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
@@ -66,6 +69,8 @@
@Mock
private Executor mExecutor;
@Mock
+ private Handler mHandler;
+ @Mock
private UserContextProvider mUserContextTracker;
private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
@@ -79,8 +84,8 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mUiEventLogger,
- mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
+ mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mHandler,
+ mUiEventLogger, mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
// Return actual context info
doReturn(mContext).when(mRecordingService).getApplicationContext();
@@ -143,4 +148,54 @@
// Then the state is set to not recording
verify(mController).updateState(false);
}
+
+ @Test
+ public void testOnSystemRequestedStop_recordingInProgress_endsRecording() throws IOException {
+ doReturn(true).when(mController).isRecording();
+
+ mRecordingService.onStopped();
+
+ verify(mScreenMediaRecorder).end();
+ }
+
+ @Test
+ public void testOnSystemRequestedStop_recordingInProgress_updatesState() {
+ doReturn(true).when(mController).isRecording();
+
+ mRecordingService.onStopped();
+
+ verify(mController).updateState(false);
+ }
+
+ @Test
+ public void testOnSystemRequestedStop_recordingIsNotInProgress_doesNotEndRecording()
+ throws IOException {
+ doReturn(false).when(mController).isRecording();
+
+ mRecordingService.onStopped();
+
+ verify(mScreenMediaRecorder, never()).end();
+ }
+
+ @Test
+ public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_releasesRecording()
+ throws IOException {
+ doReturn(true).when(mController).isRecording();
+ doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
+
+ mRecordingService.onStopped();
+
+ verify(mScreenMediaRecorder).release();
+ }
+
+ @Test
+ public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording()
+ throws IOException {
+ doReturn(true).when(mController).isRecording();
+ doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end();
+
+ assertThrows(Throwable.class, () -> mRecordingService.onStopped());
+
+ verify(mScreenMediaRecorder).release();
+ }
}
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..30c1b25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -143,7 +143,6 @@
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;
@@ -1182,11 +1181,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
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
index d6faaee..b6f8326 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
@@ -7,6 +7,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
@@ -19,6 +21,7 @@
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -32,6 +35,7 @@
@Mock private lateinit var splitShadeOverScroller: SplitShadeOverScroller
@Mock private lateinit var scrimShadeTransitionController: ScrimShadeTransitionController
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
private lateinit var controller: ShadeTransitionController
@@ -50,7 +54,9 @@
context,
splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
noOpOverScroller,
- scrimShadeTransitionController)
+ scrimShadeTransitionController,
+ statusBarStateController,
+ )
// Resetting as they are notified upon initialization.
reset(noOpOverScroller, splitShadeOverScroller)
@@ -80,6 +86,45 @@
}
@Test
+ fun onPanelStateChanged_inSplitShade_onKeyguard_forwardsToNoOpOverScroller() {
+ initLateProperties()
+ enableSplitShade()
+ setOnKeyguard()
+
+ startPanelExpansion()
+
+ verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING)
+ verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+ verifyZeroInteractions(splitShadeOverScroller)
+ }
+
+ @Test
+ fun onPanelStateChanged_inSplitShade_onLockedShade_forwardsToNoOpOverScroller() {
+ initLateProperties()
+ enableSplitShade()
+ setOnLockedShade()
+
+ startPanelExpansion()
+
+ verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING)
+ verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+ verifyZeroInteractions(splitShadeOverScroller)
+ }
+
+ @Test
+ fun onPanelExpansionChanged_inSplitShade_onUnlockedShade_forwardsToSplitShadeOverScroller() {
+ initLateProperties()
+ enableSplitShade()
+ setOnUnlockedShade()
+
+ startPanelExpansion()
+
+ verify(splitShadeOverScroller).onPanelStateChanged(STATE_OPENING)
+ verify(splitShadeOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+ verifyZeroInteractions(noOpOverScroller)
+ }
+
+ @Test
fun onPanelStateChanged_notInSplitShade_forwardsToNoOpOverScroller() {
initLateProperties()
disableSplitShade()
@@ -129,6 +174,23 @@
)
}
+ private fun setOnKeyguard() {
+ setShadeState(StatusBarState.KEYGUARD)
+ }
+
+ private fun setOnLockedShade() {
+ setShadeState(StatusBarState.SHADE_LOCKED)
+ }
+
+ private fun setOnUnlockedShade() {
+ setShadeState(StatusBarState.SHADE)
+ }
+
+ private fun setShadeState(state: Int) {
+ whenever(statusBarStateController.state).thenReturn(state)
+ whenever(statusBarStateController.currentOrUpcomingState).thenReturn(state)
+ }
+
companion object {
private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
private val DEFAULT_EXPANSION_EVENT =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
new file mode 100644
index 0000000..aaa2357
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.shared.clocks
+
+import org.mockito.Mockito.`when` as whenever
+import android.content.Context
+import android.content.ContentResolver
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.shared.plugins.PluginManager
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ClockRegistryTest : SysuiTestCase() {
+
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var mockPluginManager: PluginManager
+ @Mock private lateinit var mockClock: Clock
+ @Mock private lateinit var mockThumbnail: Drawable
+ @Mock private lateinit var mockHandler: Handler
+ @Mock private lateinit var mockContentResolver: ContentResolver
+ private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
+ private lateinit var registry: ClockRegistry
+
+ private var settingValue: String = ""
+
+ companion object {
+ private fun failFactory(): Clock {
+ fail("Unexpected call to createClock")
+ return null!!
+ }
+
+ private fun failThumbnail(): Drawable? {
+ fail("Unexpected call to getThumbnail")
+ return null
+ }
+ }
+
+ private class FakeClockPlugin : ClockProviderPlugin {
+ private val metadata = mutableListOf<ClockMetadata>()
+ private val createCallbacks = mutableMapOf<ClockId, () -> Clock>()
+ private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
+
+ override fun getClocks() = metadata
+ override fun createClock(id: ClockId): Clock = createCallbacks[id]!!()
+ override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
+
+ fun addClock(
+ id: ClockId,
+ name: String,
+ create: () -> Clock = ::failFactory,
+ getThumbnail: () -> Drawable? = ::failThumbnail
+ ) {
+ metadata.add(ClockMetadata(id, name))
+ createCallbacks[id] = create
+ thumbnailCallbacks[id] = getThumbnail
+ }
+ }
+
+ @Before
+ fun setUp() {
+ whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+
+ val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
+ registry = object : ClockRegistry(mockContext, mockPluginManager, mockHandler) {
+ override var currentClockId: ClockId
+ get() = settingValue
+ set(value) { settingValue = value }
+ }
+ verify(mockPluginManager).addPluginListener(captor.capture(),
+ eq(ClockProviderPlugin::class.java))
+ pluginListener = captor.value
+ }
+
+ @Test
+ fun pluginRegistration_CorrectState() {
+ val plugin1 = FakeClockPlugin()
+ plugin1.addClock("clock_1", "clock 1")
+ plugin1.addClock("clock_2", "clock 2")
+
+ val plugin2 = FakeClockPlugin()
+ plugin2.addClock("clock_3", "clock 3")
+ plugin2.addClock("clock_4", "clock 4")
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+ val list = registry.getClocks()
+ assertEquals(list, listOf(
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2"),
+ ClockMetadata("clock_3", "clock 3"),
+ ClockMetadata("clock_4", "clock 4")
+ ))
+ }
+
+ @Test
+ fun clockIdConflict_ErrorWithoutCrash() {
+ val plugin1 = FakeClockPlugin()
+ plugin1.addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
+ plugin1.addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })
+
+ val plugin2 = FakeClockPlugin()
+ plugin2.addClock("clock_1", "clock 1")
+ plugin2.addClock("clock_2", "clock 2")
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+ val list = registry.getClocks()
+ assertEquals(list, listOf(
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2")
+ ))
+
+ assertEquals(registry.createExampleClock("clock_1"), mockClock)
+ assertEquals(registry.createExampleClock("clock_2"), mockClock)
+ assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
+ assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
+ }
+
+ @Test
+ fun createCurrentClock_pluginConnected() {
+ val plugin1 = FakeClockPlugin()
+ plugin1.addClock("clock_1", "clock 1")
+ plugin1.addClock("clock_2", "clock 2")
+
+ settingValue = "clock_3"
+ val plugin2 = FakeClockPlugin()
+ plugin2.addClock("clock_3", "clock 3", { mockClock })
+ plugin2.addClock("clock_4", "clock 4")
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+
+ val clock = registry.createCurrentClock()
+ assertEquals(clock, mockClock)
+ }
+
+ @Test
+ fun createDefaultClock_pluginDisconnected() {
+ val plugin1 = FakeClockPlugin()
+ plugin1.addClock(DEFAULT_CLOCK_ID, "default", { mockClock })
+ plugin1.addClock("clock_2", "clock 2")
+
+ settingValue = "clock_3"
+ val plugin2 = FakeClockPlugin()
+ plugin2.addClock("clock_3", "clock 3")
+ plugin2.addClock("clock_4", "clock 4")
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+ pluginListener.onPluginDisconnected(plugin2)
+
+ val clock = registry.createCurrentClock()
+ assertEquals(clock, mockClock)
+ }
+
+ @Test
+ fun pluginRemoved_clockChanged() {
+ val plugin1 = FakeClockPlugin()
+ plugin1.addClock("clock_1", "clock 1")
+ plugin1.addClock("clock_2", "clock 2")
+
+ settingValue = "clock_3"
+ val plugin2 = FakeClockPlugin()
+ plugin2.addClock("clock_3", "clock 3", { mockClock })
+ plugin2.addClock("clock_4", "clock 4")
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+
+ var changeCallCount = 0
+ registry.registerClockChangeListener({ changeCallCount++ })
+
+ pluginListener.onPluginDisconnected(plugin1)
+ assertEquals(0, changeCallCount)
+
+ pluginListener.onPluginDisconnected(plugin2)
+ assertEquals(1, changeCallCount)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 16b0376..aeef6b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -19,6 +19,8 @@
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
@@ -41,6 +43,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -50,6 +53,7 @@
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
@@ -91,7 +95,9 @@
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -138,9 +144,14 @@
@Mock private NotificationMediaManager mNotificationMediaManager;
@Mock private NotificationRowBinder mNotificationRowBinder;
@Mock private NotificationListener mNotificationListener;
+ @Mock private IStatusBarService mStatusBarService;
+
+ private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
private int mId;
private NotificationEntry mEntry;
+ private DismissedByUserStats mStats;
private StatusBarNotification mSbn;
private NotificationEntryManager mEntryManager;
@@ -191,6 +202,7 @@
Handler.createAsync(TestableLooper.get(this).getLooper()));
mEntry = createNotification();
+ mStats = defaultStats(mEntry);
mSbn = mEntry.getSbn();
when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
@@ -201,9 +213,10 @@
() -> mNotificationRowBinder,
() -> mRemoteInputManager,
mLeakDetector,
- mock(IStatusBarService.class),
+ mStatusBarService,
NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
- mock(DumpManager.class)
+ mock(DumpManager.class),
+ mBgExecutor
);
mEntryManager.initialize(
mNotificationListener,
@@ -316,6 +329,31 @@
}
@Test
+ public void testPerformRemoveNotification_sendRemovalToServer() throws RemoteException {
+ // GIVEN an entry manager with a notification
+ mEntryManager.addActiveNotificationForTest(mEntry);
+
+ // GIVEN interceptor that doesn't intercept
+ when(mRemoveInterceptor.onNotificationRemoveRequested(
+ eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
+ .thenReturn(false);
+
+ // WHEN the notification entry is removed
+ mEntryManager.performRemoveNotification(mSbn, mStats, REASON_CANCEL);
+
+ // THEN notification removal is sent to the server
+ FakeExecutor.exhaustExecutors(mBgExecutor);
+ verify(mStatusBarService).onNotificationClear(
+ mSbn.getPackageName(),
+ mSbn.getUser().getIdentifier(),
+ mSbn.getKey(),
+ mStats.dismissalSurface,
+ mStats.dismissalSentiment,
+ mStats.notificationVisibility);
+ verifyNoMoreInteractions(mStatusBarService);
+ }
+
+ @Test
public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() {
mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON);
@@ -573,23 +611,6 @@
any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
}
- private NotificationEntry createNotification() {
- Notification.Builder n = new Notification.Builder(mContext, "id")
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
-
- return new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setId(mId++)
- .setNotification(n.build())
- .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .build();
- }
-
/* Tests annexed from NotificationDataTest go here */
@Test
@@ -713,4 +734,28 @@
return mManagedNotifs.contains(notificationKey);
}
}
+
+ private NotificationEntry createNotification() {
+ Notification.Builder n = new Notification.Builder(mContext, "id")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text");
+
+ return new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setId(mId++)
+ .setNotification(n.build())
+ .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .build();
+ }
+
+ private static DismissedByUserStats defaultStats(NotificationEntry entry) {
+ return new DismissedByUserStats(
+ DISMISSAL_SHADE,
+ DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index ef763d9..4df99be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -92,6 +92,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -146,6 +147,7 @@
private NoManSimulator mNoMan;
private FakeSystemClock mClock = new FakeSystemClock();
+ private FakeExecutor mBgExecutor = new FakeExecutor(mClock);
@Before
public void setUp() {
@@ -162,6 +164,7 @@
mNotifPipelineFlags,
mLogger,
mMainHandler,
+ mBgExecutor,
mEulogizer,
mock(DumpManager.class));
mCollection.attach(mGroupCoalescer);
@@ -461,6 +464,8 @@
DismissedByUserStats stats = defaultStats(entry2);
mCollection.dismissNotification(entry2, defaultStats(entry2));
+ FakeExecutor.exhaustExecutors(mBgExecutor);
+
// THEN we send the dismissal to system server
verify(mStatusBarService).onNotificationClear(
notif2.sbn.getPackageName(),
@@ -674,6 +679,8 @@
mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
stats);
+ FakeExecutor.exhaustExecutors(mBgExecutor);
+
// THEN we send the dismissal to system server
verify(mStatusBarService).onNotificationClear(
eq(notif.sbn.getPackageName()),
@@ -1211,6 +1218,7 @@
new Pair<>(entry2, defaultStats(entry2))));
// THEN we send the dismissals to system server
+ FakeExecutor.exhaustExecutors(mBgExecutor);
verify(mStatusBarService).onNotificationClear(
notif1.sbn.getPackageName(),
notif1.sbn.getUser().getIdentifier(),
@@ -1577,6 +1585,7 @@
// WHEN finally dismissing
onDismiss.run();
+ FakeExecutor.exhaustExecutors(mBgExecutor);
verify(mStatusBarService).onNotificationClear(any(), anyInt(), eq(notifEvent.key),
anyInt(), anyInt(), any());
verifyNoMoreInteractions(mStatusBarService);
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/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 251ac7d..bf7549a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -194,7 +194,8 @@
mLeakDetector,
mock(IStatusBarService.class),
NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
- mock(DumpManager.class)
+ mock(DumpManager.class),
+ mBgExecutor
);
mEntryManager.initialize(
mNotificationListener,
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/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 3f14494..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);
@@ -124,7 +127,7 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
@@ -134,11 +137,11 @@
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
- assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
@Test
@@ -234,7 +237,7 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
@@ -246,7 +249,7 @@
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
@@ -257,12 +260,12 @@
when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
// Make sure they start out as visible
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.VISIBLE, getClockView().getVisibility());
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.GONE, getClockView().getVisibility());
}
@@ -273,14 +276,14 @@
when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
// Make sure they start out as visible
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.VISIBLE, getClockView().getVisibility());
fragment.onDozingChanged(true);
// When this callback is triggered, we want to make sure the clock and system info
// visibilities are recalculated. Since dozing=true, they shouldn't be visible.
- assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.GONE, getClockView().getVisibility());
}
@@ -386,7 +389,8 @@
),
mOperatorNameViewControllerFactory,
mSecureSettings,
- mExecutor);
+ mExecutor,
+ mDumpManager);
}
private void setUpDaggerComponent() {
@@ -419,7 +423,7 @@
return mFragment.getView().findViewById(R.id.clock);
}
- private View getSystemIconAreaView() {
- return mFragment.getView().findViewById(R.id.system_icon_area);
+ private View getEndSideContentView() {
+ return mFragment.getView().findViewById(R.id.status_bar_end_side_content);
}
}
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 2b37b9e..9c21366 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -24,22 +24,16 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
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.compatui.CompatUI;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -71,32 +65,25 @@
@Mock ConfigurationController mConfigurationController;
@Mock KeyguardStateController mKeyguardStateController;
@Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock NavigationModeController mNavigationModeController;
@Mock ScreenLifecycle mScreenLifecycle;
@Mock SysUiState mSysUiState;
@Mock Pip mPip;
@Mock SplitScreen mSplitScreen;
@Mock OneHanded mOneHanded;
- @Mock HideDisplayCutout mHideDisplayCutout;
@Mock WakefulnessLifecycle mWakefulnessLifecycle;
@Mock ProtoTracer mProtoTracer;
- @Mock ShellCommandHandler mShellCommandHandler;
- @Mock CompatUI mCompatUI;
@Mock UserInfoController mUserInfoController;
@Mock ShellExecutor mSysUiMainExecutor;
- @Mock DragAndDrop mDragAndDrop;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
- Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
- Optional.of(mShellCommandHandler), Optional.of(mCompatUI),
- Optional.of(mDragAndDrop),
- 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
@@ -107,27 +94,12 @@
}
@Test
- public void initSplitScreen_registersCallbacks() {
- mWMShell.initSplitScreen(mSplitScreen);
-
- verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
- }
-
- @Test
public void initOneHanded_registersCallbacks() {
mWMShell.initOneHanded(mOneHanded);
- verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class));
verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class));
}
-
- @Test
- public void initCompatUI_registersCallbacks() {
- mWMShell.initCompatUi(mCompatUI);
-
- verify(mKeyguardStateController).addCallback(any(KeyguardStateController.Callback.class));
- }
}
diff --git a/services/contentcapture/Android.bp b/services/contentcapture/Android.bp
index 434f239..5392c2c 100644
--- a/services/contentcapture/Android.bp
+++ b/services/contentcapture/Android.bp
@@ -17,6 +17,9 @@
java_library_static {
name: "services.contentcapture",
defaults: ["platform_service_defaults"],
- srcs: [":services.contentcapture-sources"],
+ srcs: [
+ ":services.contentcapture-sources",
+ "java/**/*.logtags",
+ ],
libs: ["services.core"],
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 41a7592..0428b23 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -60,6 +60,7 @@
import android.service.voice.VoiceInteractionManagerInternal;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -88,6 +89,11 @@
private static final String TAG = ContentCapturePerUserService.class.getSimpleName();
+ private static final int EVENT_LOG_CONNECT_STATE_DIED = 0;
+ static final int EVENT_LOG_CONNECT_STATE_CONNECTED = 1;
+ static final int EVENT_LOG_CONNECT_STATE_DISCONNECTED = 2;
+
+
@GuardedBy("mLock")
private final SparseArray<ContentCaptureServerSession> mSessions = new SparseArray<>();
@@ -190,9 +196,13 @@
Slog.w(TAG, "remote service died: " + service);
synchronized (mLock) {
mZombie = true;
+ ComponentName serviceComponent = getServiceComponentName();
writeServiceEvent(
FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_REMOTE_SERVICE_DIED,
- getServiceComponentName());
+ serviceComponent);
+ EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId,
+ serviceComponent != null ? serviceComponent.flattenToShortString() : "",
+ EVENT_LOG_CONNECT_STATE_DIED);
}
}
@@ -614,11 +624,16 @@
? "null_activities" : activities.size() + " activities") + ")"
+ " for user " + mUserId);
}
+ int packageCount = packages != null ? packages.size() : 0;
+ int activityCount = activities != null ? activities.size() : 0;
ArraySet<String> oldList =
mMaster.mGlobalContentCaptureOptions.getWhitelistedPackages(mUserId);
+ EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId, oldList.size());
mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities);
+ EventLog.writeEvent(EventLogTags.CC_SET_ALLOWLIST, mUserId,
+ packageCount, activityCount);
writeSetWhitelistEvent(getServiceComponentName(), packages, activities);
updateContentCaptureOptions(oldList);
@@ -699,12 +714,14 @@
private void updateContentCaptureOptions(@Nullable ArraySet<String> oldList) {
ArraySet<String> adding = mMaster.mGlobalContentCaptureOptions
.getWhitelistedPackages(mUserId);
+ EventLog.writeEvent(EventLogTags.CC_CURRENT_ALLOWLIST, mUserId, adding.size());
if (oldList != null && adding != null) {
adding.removeAll(oldList);
}
int N = adding != null ? adding.size() : 0;
+ EventLog.writeEvent(EventLogTags.CC_UPDATE_OPTIONS, mUserId, N);
for (int i = 0; i < N; i++) {
String packageName = adding.valueAt(i);
ContentCaptureOptions options = mMaster.mGlobalContentCaptureOptions
diff --git a/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags b/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags
new file mode 100644
index 0000000..6722b9e
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentcapture/EventLogTags.logtags
@@ -0,0 +1,8 @@
+# See system/logging/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.server.contentcapture
+
+53200 cc_connect_state_changed (User|1|5),(component|3),(type|1)
+53201 cc_set_allowlist (User|1|5),(package_count|1),(activity_count|1)
+53202 cc_current_allowlist (User|1|5),(count|1)
+53203 cc_update_options (User|1|5),(count|1)
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 1efe55a..e22a9d0 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -31,6 +31,7 @@
import android.service.contentcapture.IContentCaptureServiceCallback;
import android.service.contentcapture.IDataShareCallback;
import android.service.contentcapture.SnapshotData;
+import android.util.EventLog;
import android.util.Slog;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.DataRemovalRequest;
@@ -47,6 +48,7 @@
private final IBinder mServerCallback;
private final int mIdleUnbindTimeoutMs;
private final ContentCapturePerUserService mPerUserService;
+ private final int mUserId;
RemoteContentCaptureService(Context context, String serviceInterface,
ComponentName serviceComponentName, IContentCaptureServiceCallback callback, int userId,
@@ -61,6 +63,7 @@
mPerUserService = perUserService;
mServerCallback = callback.asBinder();
mIdleUnbindTimeoutMs = idleUnbindTimeoutMs;
+ mUserId = userId;
// Bind right away, which will trigger a onConnected() on service's
ensureBoundLocked();
@@ -88,6 +91,9 @@
writeServiceEvent(
FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_CONNECTED,
mComponentName);
+ EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId,
+ mComponentName != null ? mComponentName.flattenToShortString() : "",
+ ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_CONNECTED);
} finally {
// Update the system-service state, in case the service reconnected after
// dying
@@ -98,6 +104,9 @@
writeServiceEvent(
FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_DISCONNECTED,
mComponentName);
+ EventLog.writeEvent(EventLogTags.CC_CONNECT_STATE_CHANGED, mUserId,
+ mComponentName != null ? mComponentName.flattenToShortString() : "",
+ ContentCapturePerUserService.EVENT_LOG_CONNECT_STATE_DISCONNECTED);
}
} catch (Exception e) {
Slog.w(mTag, "Exception calling onConnectedStateChanged(" + connected + "): " + e);
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/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index ad24cf0..262be08 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -28,8 +28,10 @@
import android.hardware.biometrics.common.OperationContext;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
+import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.Utils;
@@ -41,6 +43,10 @@
public static final String TAG = "BiometricLogger";
public static final boolean DEBUG = false;
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static int sAlsCounter;
private final int mStatsModality;
private final int mStatsAction;
@@ -345,13 +351,33 @@
if (!mLightSensorEnabled) {
mLightSensorEnabled = true;
mLastAmbientLux = 0;
- mSensorManager.registerListener(mLightSensorListener, lightSensor,
- SensorManager.SENSOR_DELAY_NORMAL);
+ int localAlsCounter;
+ synchronized (sLock) {
+ localAlsCounter = sAlsCounter++;
+ }
+
+ if (localAlsCounter == 0) {
+ mSensorManager.registerListener(mLightSensorListener, lightSensor,
+ SensorManager.SENSOR_DELAY_NORMAL);
+ } else {
+ Slog.e(TAG, "Ignoring request to subscribe to ALSProbe due to non-zero ALS"
+ + " counter: " + localAlsCounter);
+ Slog.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
}
} else {
mLightSensorEnabled = false;
mLastAmbientLux = 0;
mSensorManager.unregisterListener(mLightSensorListener);
+ int localAlsCounter;
+ synchronized (sLock) {
+ localAlsCounter = --sAlsCounter;
+ }
+ if (localAlsCounter != 0) {
+ Slog.e(TAG, "Non-zero ALS counter after unsubscribing from ALSProbe: "
+ + localAlsCounter);
+ Slog.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
}
}
}
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/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
index e222c64..d238dae 100644
--- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
+++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
@@ -27,8 +27,6 @@
import android.view.SurfaceControl;
import android.view.WindowManager;
-import com.android.server.policy.WindowManagerPolicy;
-
/**
* An internal implementation of an {@link InputMonitor} that uses a spy window.
*
@@ -69,9 +67,7 @@
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
t.setInputWindowInfo(mInputSurface, mWindowHandle);
- // Gesture monitor should be above handwriting event surface, hence setting it to
- // WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER + 1
- t.setLayer(mInputSurface, WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER + 1);
+ t.setLayer(mInputSurface, Integer.MAX_VALUE);
t.setPosition(mInputSurface, 0, 0);
t.setCrop(mInputSurface, null /* crop to parent surface */);
t.show(mInputSurface);
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index 5438faa..8180e66 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -27,8 +27,6 @@
import android.view.SurfaceControl;
import android.view.WindowManager;
-import com.android.server.policy.WindowManagerPolicy;
-
final class HandwritingEventReceiverSurface {
public static final String TAG = HandwritingEventReceiverSurface.class.getSimpleName();
@@ -38,8 +36,7 @@
// is above gesture monitors, then edge-back and swipe-up gestures won't work when this surface
// is intercepting.
// TODO(b/217538817): Specify the ordering in WM by usage.
- private static final int HANDWRITING_SURFACE_LAYER =
- WindowManagerPolicy.INPUT_DISPLAY_OVERLAY_LAYER;
+ private static final int HANDWRITING_SURFACE_LAYER = Integer.MAX_VALUE - 1;
private final InputWindowHandle mWindowHandle;
private final InputChannel mClientChannel;
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/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 9bc0553..e8a3dcd 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -155,10 +155,6 @@
int FINISH_LAYOUT_REDO_ANIM = 0x0008;
/** Layer for the screen off animation */
int COLOR_FADE_LAYER = 0x40000001;
- /** Layer for Input overlays for capturing inputs for gesture detection, etc. */
- int INPUT_DISPLAY_OVERLAY_LAYER = 0x7f000000;
- /** Layer for Screen Decoration: The top most visible layer just below input overlay layers */
- int SCREEN_DECOR_DISPLAY_OVERLAY_LAYER = INPUT_DISPLAY_OVERLAY_LAYER - 1;
/**
* Register shortcuts for window manager to dispatch.
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/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index b7ddbd0..33cdd2e 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -265,7 +265,7 @@
.setContainerLayer()
.setName(name)
.setCallsite("createSurfaceForGestureMonitor")
- .setParent(dc.getOverlayLayer())
+ .setParent(dc.getSurfaceControl())
.build();
}
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 08bf7bc..53f1fe6 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -352,10 +352,6 @@
}
}
- // TODO(b/166736352): Remove this method without the need to expose to launcher.
- @Override
- public void hideCurrentInputMethod() { }
-
@Override
public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index d09068b..8cad165 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -173,11 +173,6 @@
if (isSizeChanged) {
mRoundedCornerOverlay = displayContent.findRoundedCornerOverlays();
- } else {
- // Exclude rounded corner overlay from screenshot buffer. Rounded
- // corner overlay windows are un-rotated during rotation animation
- // for a seamless transition.
- builder.setExcludeLayers(displayContent.findRoundedCornerOverlays());
}
SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d31dfee..9d6e250 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8262,7 +8262,7 @@
.setContainerLayer()
.setName("IME Handwriting Surface")
.setCallsite("getHandwritingSurfaceForDisplay")
- .setParent(dc.getOverlayLayer())
+ .setParent(dc.getSurfaceControl())
.build();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 437c934..bbb21f8 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -362,7 +362,7 @@
@Override
void assignLayer(SurfaceControl.Transaction t, int layer) {
if (mRoundedCornerOverlay) {
- super.assignLayer(t, WindowManagerPolicy.SCREEN_DECOR_DISPLAY_OVERLAY_LAYER);
+ super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1);
} else {
super.assignLayer(t, layer);
}
@@ -372,7 +372,7 @@
SurfaceControl.Builder makeSurface() {
final SurfaceControl.Builder builder = super.makeSurface();
if (mRoundedCornerOverlay) {
- builder.setParent(getDisplayContent().getOverlayLayer());
+ builder.setParent(null);
}
return builder;
}
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/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;