Merge "Skip restore of apps that have been launched or restored." into main
diff --git a/INPUT_OWNERS b/INPUT_OWNERS
index e02ba77..44b2f38 100644
--- a/INPUT_OWNERS
+++ b/INPUT_OWNERS
@@ -5,3 +5,5 @@
prabirmsp@google.com
svv@google.com
vdevmurari@google.com
+
+per-file Virtual*=file:/services/companion/java/com/android/server/companion/virtual/OWNERS
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index f6a7d2a..da8277c 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -271,7 +271,7 @@
final IBinder token = new Binder(
"android.hardware.input.VirtualDpad:" + config.getInputDeviceName());
mVirtualDevice.createVirtualDpad(config, token);
- return new VirtualDpad(mVirtualDevice, token);
+ return new VirtualDpad(config, mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -283,7 +283,7 @@
final IBinder token = new Binder(
"android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName());
mVirtualDevice.createVirtualKeyboard(config, token);
- return new VirtualKeyboard(mVirtualDevice, token);
+ return new VirtualKeyboard(config, mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -295,7 +295,7 @@
final IBinder token = new Binder(
"android.hardware.input.VirtualMouse:" + config.getInputDeviceName());
mVirtualDevice.createVirtualMouse(config, token);
- return new VirtualMouse(mVirtualDevice, token);
+ return new VirtualMouse(config, mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -308,7 +308,7 @@
final IBinder token = new Binder(
"android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName());
mVirtualDevice.createVirtualTouchscreen(config, token);
- return new VirtualTouchscreen(mVirtualDevice, token);
+ return new VirtualTouchscreen(config, mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -322,7 +322,7 @@
"android.hardware.input.VirtualNavigationTouchpad:"
+ config.getInputDeviceName());
mVirtualDevice.createVirtualNavigationTouchpad(config, token);
- return new VirtualNavigationTouchpad(mVirtualDevice, token);
+ return new VirtualNavigationTouchpad(config, mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 884d463..f532c4c 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -62,6 +62,8 @@
"mediaSharedWithParent";
private static final String ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT =
"credentialShareableWithParent";
+ private static final String ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE =
+ "authAlwaysRequiredToDisableQuietMode";
private static final String ATTR_DELETE_APP_WITH_PARENT = "deleteAppWithParent";
private static final String ATTR_ALWAYS_VISIBLE = "alwaysVisible";
@@ -80,6 +82,7 @@
INDEX_DELETE_APP_WITH_PARENT,
INDEX_ALWAYS_VISIBLE,
INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE,
+ INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
})
@Retention(RetentionPolicy.SOURCE)
private @interface PropertyIndex {
@@ -97,6 +100,7 @@
private static final int INDEX_DELETE_APP_WITH_PARENT = 10;
private static final int INDEX_ALWAYS_VISIBLE = 11;
private static final int INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE = 12;
+ private static final int INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = 13;
/** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
private long mPropertiesPresent = 0;
@@ -329,6 +333,8 @@
setShowInSettings(orig.getShowInSettings());
setHideInSettingsInQuietMode(orig.getHideInSettingsInQuietMode());
setUseParentsContacts(orig.getUseParentsContacts());
+ setAuthAlwaysRequiredToDisableQuietMode(
+ orig.isAuthAlwaysRequiredToDisableQuietMode());
}
if (hasQueryOrManagePermission) {
// Add items that require QUERY_USERS or stronger.
@@ -611,6 +617,31 @@
}
private boolean mCredentialShareableWithParent;
+ /**
+ * Returns whether the profile always requires user authentication to disable from quiet mode.
+ *
+ * <p> Settings this field to true will ensure that the credential confirmation activity is
+ * always shown whenever the user requests to disable quiet mode. The behavior of credential
+ * checks is not guaranteed when the property is false and may vary depending on user types.
+ * @hide
+ */
+ public boolean isAuthAlwaysRequiredToDisableQuietMode() {
+ if (isPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE)) {
+ return mAuthAlwaysRequiredToDisableQuietMode;
+ }
+ if (mDefaultProperties != null) {
+ return mDefaultProperties.mAuthAlwaysRequiredToDisableQuietMode;
+ }
+ throw new SecurityException(
+ "You don't have permission to query authAlwaysRequiredToDisableQuietMode");
+ }
+ /** @hide */
+ public void setAuthAlwaysRequiredToDisableQuietMode(boolean val) {
+ this.mAuthAlwaysRequiredToDisableQuietMode = val;
+ setPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE);
+ }
+ private boolean mAuthAlwaysRequiredToDisableQuietMode;
+
/*
Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
OTA update between user-parent
@@ -693,6 +724,8 @@
+ getCrossProfileIntentResolutionStrategy()
+ ", mMediaSharedWithParent=" + isMediaSharedWithParent()
+ ", mCredentialShareableWithParent=" + isCredentialShareableWithParent()
+ + ", mAuthAlwaysRequiredToDisableQuietMode="
+ + isAuthAlwaysRequiredToDisableQuietMode()
+ ", mDeleteAppWithParent=" + getDeleteAppWithParent()
+ ", mAlwaysVisible=" + getAlwaysVisible()
+ "}";
@@ -720,6 +753,8 @@
pw.println(prefix + " mMediaSharedWithParent=" + isMediaSharedWithParent());
pw.println(prefix + " mCredentialShareableWithParent="
+ isCredentialShareableWithParent());
+ pw.println(prefix + " mAuthAlwaysRequiredToDisableQuietMode="
+ + isAuthAlwaysRequiredToDisableQuietMode());
pw.println(prefix + " mDeleteAppWithParent=" + getDeleteAppWithParent());
pw.println(prefix + " mAlwaysVisible=" + getAlwaysVisible());
}
@@ -788,6 +823,9 @@
case ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT:
setCredentialShareableWithParent(parser.getAttributeBoolean(i));
break;
+ case ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE:
+ setAuthAlwaysRequiredToDisableQuietMode(parser.getAttributeBoolean(i));
+ break;
case ATTR_DELETE_APP_WITH_PARENT:
setDeleteAppWithParent(parser.getAttributeBoolean(i));
break;
@@ -853,6 +891,10 @@
serializer.attributeBoolean(null, ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT,
mCredentialShareableWithParent);
}
+ if (isPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE)) {
+ serializer.attributeBoolean(null, ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
+ mAuthAlwaysRequiredToDisableQuietMode);
+ }
if (isPresent(INDEX_DELETE_APP_WITH_PARENT)) {
serializer.attributeBoolean(null, ATTR_DELETE_APP_WITH_PARENT,
mDeleteAppWithParent);
@@ -878,6 +920,7 @@
dest.writeInt(mCrossProfileIntentResolutionStrategy);
dest.writeBoolean(mMediaSharedWithParent);
dest.writeBoolean(mCredentialShareableWithParent);
+ dest.writeBoolean(mAuthAlwaysRequiredToDisableQuietMode);
dest.writeBoolean(mDeleteAppWithParent);
dest.writeBoolean(mAlwaysVisible);
}
@@ -901,6 +944,7 @@
mCrossProfileIntentResolutionStrategy = source.readInt();
mMediaSharedWithParent = source.readBoolean();
mCredentialShareableWithParent = source.readBoolean();
+ mAuthAlwaysRequiredToDisableQuietMode = source.readBoolean();
mDeleteAppWithParent = source.readBoolean();
mAlwaysVisible = source.readBoolean();
}
@@ -941,6 +985,7 @@
CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT;
private boolean mMediaSharedWithParent = false;
private boolean mCredentialShareableWithParent = false;
+ private boolean mAuthAlwaysRequiredToDisableQuietMode = false;
private boolean mDeleteAppWithParent = false;
private boolean mAlwaysVisible = false;
@@ -1010,6 +1055,14 @@
return this;
}
+ /** Sets the value for {@link #mAuthAlwaysRequiredToDisableQuietMode} */
+ public Builder setAuthAlwaysRequiredToDisableQuietMode(
+ boolean authAlwaysRequiredToDisableQuietMode) {
+ mAuthAlwaysRequiredToDisableQuietMode =
+ authAlwaysRequiredToDisableQuietMode;
+ return this;
+ }
+
/** Sets the value for {@link #mDeleteAppWithParent}*/
public Builder setDeleteAppWithParent(boolean deleteAppWithParent) {
mDeleteAppWithParent = deleteAppWithParent;
@@ -1036,6 +1089,7 @@
mCrossProfileIntentResolutionStrategy,
mMediaSharedWithParent,
mCredentialShareableWithParent,
+ mAuthAlwaysRequiredToDisableQuietMode,
mDeleteAppWithParent,
mAlwaysVisible);
}
@@ -1053,6 +1107,7 @@
@CrossProfileIntentResolutionStrategy int crossProfileIntentResolutionStrategy,
boolean mediaSharedWithParent,
boolean credentialShareableWithParent,
+ boolean authAlwaysRequiredToDisableQuietMode,
boolean deleteAppWithParent,
boolean alwaysVisible) {
mDefaultProperties = null;
@@ -1067,6 +1122,8 @@
setCrossProfileIntentResolutionStrategy(crossProfileIntentResolutionStrategy);
setMediaSharedWithParent(mediaSharedWithParent);
setCredentialShareableWithParent(credentialShareableWithParent);
+ setAuthAlwaysRequiredToDisableQuietMode(
+ authAlwaysRequiredToDisableQuietMode);
setDeleteAppWithParent(deleteAppWithParent);
setAlwaysVisible(alwaysVisible);
}
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index acdc0fa..d33eadc 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -27,7 +27,6 @@
* <p>
* This class is not thread-safe.
* </p>
- * Note that this class is unrelated to {@link SQLiteRawStatement}.
*/
public final class SQLiteStatement extends SQLiteProgram {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java
index 8133472..7f2d8a0 100644
--- a/core/java/android/hardware/input/VirtualDpad.java
+++ b/core/java/android/hardware/input/VirtualDpad.java
@@ -52,8 +52,8 @@
KeyEvent.KEYCODE_DPAD_CENTER)));
/** @hide */
- public VirtualDpad(IVirtualDevice virtualDevice, IBinder token) {
- super(virtualDevice, token);
+ public VirtualDpad(VirtualDpadConfig config, IVirtualDevice virtualDevice, IBinder token) {
+ super(config, virtualDevice, token);
}
/**
diff --git a/core/java/android/hardware/input/VirtualInputDevice.java b/core/java/android/hardware/input/VirtualInputDevice.java
index 772ba8e..931e1ff 100644
--- a/core/java/android/hardware/input/VirtualInputDevice.java
+++ b/core/java/android/hardware/input/VirtualInputDevice.java
@@ -42,9 +42,12 @@
*/
protected final IBinder mToken;
+ protected final VirtualInputDeviceConfig mConfig;
+
/** @hide */
- VirtualInputDevice(
+ VirtualInputDevice(VirtualInputDeviceConfig config,
IVirtualDevice virtualDevice, IBinder token) {
+ mConfig = config;
mVirtualDevice = virtualDevice;
mToken = token;
}
@@ -70,4 +73,9 @@
throw e.rethrowFromSystemServer();
}
}
+
+ @Override
+ public String toString() {
+ return mConfig.toString();
+ }
}
diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
index d3dacc9..a8caa58 100644
--- a/core/java/android/hardware/input/VirtualInputDeviceConfig.java
+++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
@@ -91,6 +91,22 @@
dest.writeString8(mInputDeviceName);
}
+ @Override
+ public String toString() {
+ return getClass().getName() + "( "
+ + " name=" + mInputDeviceName
+ + " vendorId=" + mVendorId
+ + " productId=" + mProductId
+ + " associatedDisplayId=" + mAssociatedDisplayId
+ + additionalFieldsToString() + ")";
+ }
+
+ /** @hide */
+ @NonNull
+ String additionalFieldsToString() {
+ return "";
+ }
+
/**
* A builder for {@link VirtualInputDeviceConfig}
*
diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java
index dc47f08..c0102bf 100644
--- a/core/java/android/hardware/input/VirtualKeyEvent.java
+++ b/core/java/android/hardware/input/VirtualKeyEvent.java
@@ -172,6 +172,7 @@
KeyEvent.KEYCODE_BREAK,
KeyEvent.KEYCODE_BACK,
KeyEvent.KEYCODE_FORWARD,
+ KeyEvent.KEYCODE_LANGUAGE_SWITCH,
})
@Retention(RetentionPolicy.SOURCE)
public @interface SupportedKeycode {
@@ -205,6 +206,14 @@
return 0;
}
+ @Override
+ public String toString() {
+ return "VirtualKeyEvent("
+ + " action=" + KeyEvent.actionToString(mAction)
+ + " keyCode=" + KeyEvent.keyCodeToString(mKeyCode)
+ + " eventTime(ns)=" + mEventTimeNanos;
+ }
+
/**
* Returns the key code associated with this event.
*/
diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java
index e569dbf..c90f893 100644
--- a/core/java/android/hardware/input/VirtualKeyboard.java
+++ b/core/java/android/hardware/input/VirtualKeyboard.java
@@ -39,8 +39,9 @@
private final int mUnsupportedKeyCode = KeyEvent.KEYCODE_DPAD_CENTER;
/** @hide */
- public VirtualKeyboard(IVirtualDevice virtualDevice, IBinder token) {
- super(virtualDevice, token);
+ public VirtualKeyboard(VirtualKeyboardConfig config,
+ IVirtualDevice virtualDevice, IBinder token) {
+ super(config, virtualDevice, token);
}
/**
diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.java b/core/java/android/hardware/input/VirtualKeyboardConfig.java
index 6d03065..96a1a36 100644
--- a/core/java/android/hardware/input/VirtualKeyboardConfig.java
+++ b/core/java/android/hardware/input/VirtualKeyboardConfig.java
@@ -99,6 +99,12 @@
dest.writeString8(mLayoutType);
}
+ @Override
+ @NonNull
+ String additionalFieldsToString() {
+ return " languageTag=" + mLanguageTag + " layoutType=" + mLayoutType;
+ }
+
/**
* Builder for creating a {@link VirtualKeyboardConfig}.
*/
diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java
index 7eba2b8..51f3f69 100644
--- a/core/java/android/hardware/input/VirtualMouse.java
+++ b/core/java/android/hardware/input/VirtualMouse.java
@@ -38,8 +38,8 @@
public class VirtualMouse extends VirtualInputDevice {
/** @hide */
- public VirtualMouse(IVirtualDevice virtualDevice, IBinder token) {
- super(virtualDevice, token);
+ public VirtualMouse(VirtualMouseConfig config, IVirtualDevice virtualDevice, IBinder token) {
+ super(config, virtualDevice, token);
}
/**
diff --git a/core/java/android/hardware/input/VirtualMouseButtonEvent.java b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
index dfdd3b4..fc42b15 100644
--- a/core/java/android/hardware/input/VirtualMouseButtonEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
@@ -110,6 +110,14 @@
return 0;
}
+ @Override
+ public String toString() {
+ return "VirtualMouseButtonEvent("
+ + " action=" + MotionEvent.actionToString(mAction)
+ + " button=" + MotionEvent.buttonStateToString(mButtonCode)
+ + " eventTime(ns)=" + mEventTimeNanos;
+ }
+
/**
* Returns the button code associated with this event.
*/
diff --git a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
index e6ad118..2a42cfc 100644
--- a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
@@ -61,6 +61,14 @@
return 0;
}
+ @Override
+ public String toString() {
+ return "VirtualMouseRelativeEvent("
+ + " x=" + mRelativeX
+ + " y=" + mRelativeY
+ + " eventTime(ns)=" + mEventTimeNanos;
+ }
+
/**
* Returns the relative x-axis movement, in pixels.
*/
diff --git a/core/java/android/hardware/input/VirtualMouseScrollEvent.java b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
index 4d0a157..c89c188 100644
--- a/core/java/android/hardware/input/VirtualMouseScrollEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
@@ -65,6 +65,14 @@
return 0;
}
+ @Override
+ public String toString() {
+ return "VirtualMouseScrollEvent("
+ + " x=" + mXAxisMovement
+ + " y=" + mYAxisMovement
+ + " eventTime(ns)=" + mEventTimeNanos;
+ }
+
/**
* Returns the x-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values
* indicate scrolling upward; negative values, downward.
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpad.java b/core/java/android/hardware/input/VirtualNavigationTouchpad.java
index 2854034..61d72e2 100644
--- a/core/java/android/hardware/input/VirtualNavigationTouchpad.java
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpad.java
@@ -40,8 +40,9 @@
public class VirtualNavigationTouchpad extends VirtualInputDevice {
/** @hide */
- public VirtualNavigationTouchpad(IVirtualDevice virtualDevice, IBinder token) {
- super(virtualDevice, token);
+ public VirtualNavigationTouchpad(VirtualNavigationTouchpadConfig config,
+ IVirtualDevice virtualDevice, IBinder token) {
+ super(config, virtualDevice, token);
}
/**
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
index 8935efa..75f7b3e 100644
--- a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
@@ -70,6 +70,12 @@
dest.writeInt(mWidth);
}
+ @Override
+ @NonNull
+ String additionalFieldsToString() {
+ return " width=" + mWidth + " height=" + mHeight;
+ }
+
@NonNull
public static final Creator<VirtualNavigationTouchpadConfig> CREATOR =
new Creator<VirtualNavigationTouchpadConfig>() {
diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java
index 2695a79..7936dfe 100644
--- a/core/java/android/hardware/input/VirtualTouchEvent.java
+++ b/core/java/android/hardware/input/VirtualTouchEvent.java
@@ -138,6 +138,19 @@
return 0;
}
+ @Override
+ public String toString() {
+ return "VirtualTouchEvent("
+ + " pointerId=" + mPointerId
+ + " toolType=" + MotionEvent.toolTypeToString(mToolType)
+ + " action=" + MotionEvent.actionToString(mAction)
+ + " x=" + mX
+ + " y=" + mY
+ + " pressure=" + mPressure
+ + " majorAxisSize=" + mMajorAxisSize
+ + " eventTime(ns)=" + mEventTimeNanos;
+ }
+
/**
* Returns the pointer id associated with this event.
*/
diff --git a/core/java/android/hardware/input/VirtualTouchscreen.java b/core/java/android/hardware/input/VirtualTouchscreen.java
index 0d07753..4ac439e 100644
--- a/core/java/android/hardware/input/VirtualTouchscreen.java
+++ b/core/java/android/hardware/input/VirtualTouchscreen.java
@@ -34,8 +34,9 @@
@SystemApi
public class VirtualTouchscreen extends VirtualInputDevice {
/** @hide */
- public VirtualTouchscreen(IVirtualDevice virtualDevice, IBinder token) {
- super(virtualDevice, token);
+ public VirtualTouchscreen(VirtualTouchscreenConfig config,
+ IVirtualDevice virtualDevice, IBinder token) {
+ super(config, virtualDevice, token);
}
/**
diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.java b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
index aac341cc..6308459 100644
--- a/core/java/android/hardware/input/VirtualTouchscreenConfig.java
+++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
@@ -69,6 +69,12 @@
dest.writeInt(mHeight);
}
+ @Override
+ @NonNull
+ String additionalFieldsToString() {
+ return " width=" + mWidth + " height=" + mHeight;
+ }
+
@NonNull
public static final Creator<VirtualTouchscreenConfig> CREATOR =
new Creator<VirtualTouchscreenConfig>() {
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index 0a6be20..eda80c8 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -85,14 +85,14 @@
/**
* Interface data activity status is changed.
*
- * @param transportType The transport type of the data activity change.
+ * @param label label of the data activity change.
* @param active True if the interface is actively transmitting data, false if it is idle.
* @param tsNanos Elapsed realtime in nanos when the state of the network interface changed.
* @param uid Uid of this event. It represents the uid that was responsible for waking the
* radio. For those events that are reported by system itself, not from specific uid,
* use -1 for the events which means no uid.
*/
- void interfaceClassDataActivityChanged(int transportType, boolean active, long tsNanos, int uid);
+ void interfaceClassDataActivityChanged(int label, boolean active, long tsNanos, int uid);
/**
* Information about available DNS servers has been received.
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index daec1721..13572fb 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -832,10 +832,16 @@
/**
* Returns true if the current process is a 64-bit runtime.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static final boolean is64Bit() {
return VMRuntime.getRuntime().is64Bit();
}
+ /** @hide */
+ public static final boolean is64Bit$ravenwood() {
+ return "amd64".equals(System.getProperty("os.arch"));
+ }
+
private static SomeArgs sIdentity$ravenwood;
/** @hide */
@@ -906,6 +912,7 @@
* {@link #myUid()} in that a particular user will have multiple
* distinct apps running under it each with their own uid.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static UserHandle myUserHandle() {
return UserHandle.of(UserHandle.getUserId(myUid()));
}
@@ -914,6 +921,7 @@
* Returns whether the given uid belongs to a system core component or not.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isCoreUid(int uid) {
return UserHandle.isCore(uid);
}
@@ -924,6 +932,7 @@
* @return Whether the uid corresponds to an application sandbox running in
* a specific user.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isApplicationUid(int uid) {
return UserHandle.isApp(uid);
}
@@ -931,6 +940,7 @@
/**
* Returns whether the current process is in an isolated sandbox.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isIsolated() {
return isIsolated(myUid());
}
@@ -942,6 +952,7 @@
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isIsolated(int uid) {
return isIsolatedUid(uid);
}
@@ -949,6 +960,7 @@
/**
* Returns whether the process with the given {@code uid} is an isolated sandbox.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isIsolatedUid(int uid) {
uid = UserHandle.getAppId(uid);
return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
@@ -962,6 +974,7 @@
*/
@SystemApi(client = MODULE_LIBRARIES)
@TestApi
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isSdkSandboxUid(int uid) {
uid = UserHandle.getAppId(uid);
return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID);
@@ -975,6 +988,7 @@
*/
@SystemApi(client = MODULE_LIBRARIES)
@TestApi
+ @android.ravenwood.annotation.RavenwoodKeep
public static final int getAppUidForSdkSandboxUid(int uid) {
return uid - (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
}
@@ -987,6 +1001,7 @@
*/
@SystemApi(client = MODULE_LIBRARIES)
@TestApi
+ @android.ravenwood.annotation.RavenwoodKeep
public static final int toSdkSandboxUid(int uid) {
return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
}
@@ -994,6 +1009,7 @@
/**
* Returns whether the current process is a sdk sandbox process.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static final boolean isSdkSandbox() {
return isSdkSandboxUid(myUid());
}
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index cac7f3b..0644ef1 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -36,6 +36,7 @@
/**
* Representation of a user on the device.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class UserHandle implements Parcelable {
// NOTE: keep logic in sync with system/core/libcutils/multiuser.c
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d9c6bee..2419a4c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -204,6 +204,8 @@
* the user in locked state so that a direct boot aware DPC could reset the password.
* Should not be used together with
* {@link #QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED} or an exception will be thrown.
+ * This flag is currently only allowed for {@link #isManagedProfile() managed profiles};
+ * usage on other profiles may result in an Exception.
* @hide
*/
public static final int QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL = 0x2;
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index 33058d8..2a33caa 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -26,6 +26,7 @@
import com.android.internal.util.ArtBinaryXmlSerializer;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.BinaryXmlPullParser;
import com.android.modules.utils.BinaryXmlSerializer;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -38,6 +39,7 @@
import org.xml.sax.XMLReader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedInputStream;
@@ -115,6 +117,7 @@
/**
* Returns a new pull parser with namespace support.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static XmlPullParser newPullParser() {
try {
XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
@@ -126,6 +129,12 @@
}
}
+ /** @hide */
+ public static XmlPullParser newPullParser$ravenwood() {
+ // TODO: remove once we're linking against libcore
+ return new BinaryXmlPullParser();
+ }
+
/**
* Creates a new {@link TypedXmlPullParser} which is optimized for use
* inside the system, typically by supporting only a basic set of features.
@@ -136,10 +145,17 @@
* @hide
*/
@SuppressWarnings("AndroidFrameworkEfficientXml")
+ @android.ravenwood.annotation.RavenwoodReplace
public static @NonNull TypedXmlPullParser newFastPullParser() {
return XmlUtils.makeTyped(newPullParser());
}
+ /** @hide */
+ public static TypedXmlPullParser newFastPullParser$ravenwood() {
+ // TODO: remove once we're linking against libcore
+ return new BinaryXmlPullParser();
+ }
+
/**
* Creates a new {@link XmlPullParser} that reads XML documents using a
* custom binary wire protocol which benchmarking has shown to be 8.5x
@@ -148,10 +164,17 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static @NonNull TypedXmlPullParser newBinaryPullParser() {
return new ArtBinaryXmlPullParser();
}
+ /** @hide */
+ public static TypedXmlPullParser newBinaryPullParser$ravenwood() {
+ // TODO: remove once we're linking against libcore
+ return new BinaryXmlPullParser();
+ }
+
/**
* Creates a new {@link XmlPullParser} which is optimized for use inside the
* system, typically by supporting only a basic set of features.
@@ -166,6 +189,7 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
throws IOException {
final byte[] magic = new byte[4];
@@ -198,13 +222,33 @@
return xml;
}
+ /** @hide */
+ public static @NonNull TypedXmlPullParser resolvePullParser$ravenwood(@NonNull InputStream in)
+ throws IOException {
+ // TODO: remove once we're linking against libcore
+ final TypedXmlPullParser xml = new BinaryXmlPullParser();
+ try {
+ xml.setInput(in, StandardCharsets.UTF_8.name());
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ return xml;
+ }
+
/**
* Creates a new xml serializer.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static XmlSerializer newSerializer() {
return XmlObjectFactory.newXmlSerializer();
}
+ /** @hide */
+ public static XmlSerializer newSerializer$ravenwood() {
+ // TODO: remove once we're linking against libcore
+ return new BinaryXmlSerializer();
+ }
+
/**
* Creates a new {@link XmlSerializer} which is optimized for use inside the
* system, typically by supporting only a basic set of features.
@@ -215,10 +259,17 @@
* @hide
*/
@SuppressWarnings("AndroidFrameworkEfficientXml")
+ @android.ravenwood.annotation.RavenwoodReplace
public static @NonNull TypedXmlSerializer newFastSerializer() {
return XmlUtils.makeTyped(new FastXmlSerializer());
}
+ /** @hide */
+ public static @NonNull TypedXmlSerializer newFastSerializer$ravenwood() {
+ // TODO: remove once we're linking against libcore
+ return new BinaryXmlSerializer();
+ }
+
/**
* Creates a new {@link XmlSerializer} that writes XML documents using a
* custom binary wire protocol which benchmarking has shown to be 4.4x
@@ -227,10 +278,17 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static @NonNull TypedXmlSerializer newBinarySerializer() {
return new ArtBinaryXmlSerializer();
}
+ /** @hide */
+ public static @NonNull TypedXmlSerializer newBinarySerializer$ravenwood() {
+ // TODO: remove once we're linking against libcore
+ return new BinaryXmlSerializer();
+ }
+
/**
* Creates a new {@link XmlSerializer} which is optimized for use inside the
* system, typically by supporting only a basic set of features.
@@ -245,6 +303,7 @@
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public static @NonNull TypedXmlSerializer resolveSerializer(@NonNull OutputStream out)
throws IOException {
final TypedXmlSerializer xml;
@@ -257,6 +316,15 @@
return xml;
}
+ /** @hide */
+ public static @NonNull TypedXmlSerializer resolveSerializer$ravenwood(@NonNull OutputStream out)
+ throws IOException {
+ // TODO: remove once we're linking against libcore
+ final TypedXmlSerializer xml = new BinaryXmlSerializer();
+ xml.setOutput(out, StandardCharsets.UTF_8.name());
+ return xml;
+ }
+
/**
* Copy the first XML document into the second document.
* <p>
diff --git a/core/java/com/android/internal/util/ArtFastDataInput.java b/core/java/com/android/internal/util/ArtFastDataInput.java
index 3e8916c..768ea82 100644
--- a/core/java/com/android/internal/util/ArtFastDataInput.java
+++ b/core/java/com/android/internal/util/ArtFastDataInput.java
@@ -21,6 +21,8 @@
import com.android.modules.utils.FastDataInput;
+import dalvik.system.VMRuntime;
+
import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
@@ -35,13 +37,14 @@
*/
public class ArtFastDataInput extends FastDataInput {
private static AtomicReference<ArtFastDataInput> sInCache = new AtomicReference<>();
+ private static VMRuntime sRuntime = VMRuntime.getRuntime();
private final long mBufferPtr;
public ArtFastDataInput(@NonNull InputStream in, int bufferSize) {
super(in, bufferSize);
- mBufferPtr = mRuntime.addressOf(mBuffer);
+ mBufferPtr = sRuntime.addressOf(mBuffer);
}
/**
@@ -66,6 +69,7 @@
* Release a {@link ArtFastDataInput} to potentially be recycled. You must not
* interact with the object after releasing it.
*/
+ @Override
public void release() {
super.release();
@@ -76,6 +80,11 @@
}
@Override
+ public byte[] newByteArray(int bufferSize) {
+ return (byte[]) sRuntime.newNonMovableArray(byte.class, bufferSize);
+ }
+
+ @Override
public String readUTF() throws IOException {
// Attempt to read directly from buffer space if there's enough room,
// otherwise fall back to chunking into place
@@ -86,9 +95,9 @@
mBufferPos += len;
return res;
} else {
- final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
+ final byte[] tmp = (byte[]) sRuntime.newNonMovableArray(byte.class, len + 1);
readFully(tmp, 0, len);
- return CharsetUtils.fromModifiedUtf8Bytes(mRuntime.addressOf(tmp), 0, len);
+ return CharsetUtils.fromModifiedUtf8Bytes(sRuntime.addressOf(tmp), 0, len);
}
}
}
diff --git a/core/java/com/android/internal/util/ArtFastDataOutput.java b/core/java/com/android/internal/util/ArtFastDataOutput.java
index ac595b6..360ddb8 100644
--- a/core/java/com/android/internal/util/ArtFastDataOutput.java
+++ b/core/java/com/android/internal/util/ArtFastDataOutput.java
@@ -21,6 +21,8 @@
import com.android.modules.utils.FastDataOutput;
+import dalvik.system.VMRuntime;
+
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStream;
@@ -35,13 +37,14 @@
*/
public class ArtFastDataOutput extends FastDataOutput {
private static AtomicReference<ArtFastDataOutput> sOutCache = new AtomicReference<>();
+ private static VMRuntime sRuntime = VMRuntime.getRuntime();
private final long mBufferPtr;
public ArtFastDataOutput(@NonNull OutputStream out, int bufferSize) {
super(out, bufferSize);
- mBufferPtr = mRuntime.addressOf(mBuffer);
+ mBufferPtr = sRuntime.addressOf(mBuffer);
}
/**
@@ -73,6 +76,11 @@
}
@Override
+ public byte[] newByteArray(int bufferSize) {
+ return (byte[]) sRuntime.newNonMovableArray(byte.class, bufferSize);
+ }
+
+ @Override
public void writeUTF(String s) throws IOException {
// Attempt to write directly to buffer space if there's enough room,
// otherwise fall back to chunking into place
@@ -94,8 +102,8 @@
// Negative value indicates buffer was too small and we need to
// allocate a temporary buffer for encoding
len = -len;
- final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
- CharsetUtils.toModifiedUtf8Bytes(s, mRuntime.addressOf(tmp), 0, tmp.length);
+ final byte[] tmp = (byte[]) sRuntime.newNonMovableArray(byte.class, len + 1);
+ CharsetUtils.toModifiedUtf8Bytes(s, sRuntime.addressOf(tmp), 0, tmp.length);
writeShort(len);
write(tmp, 0, len);
}
diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java
index 139b88b..61e017d 100644
--- a/core/java/com/android/server/net/BaseNetworkObserver.java
+++ b/core/java/com/android/server/net/BaseNetworkObserver.java
@@ -64,7 +64,7 @@
}
@Override
- public void interfaceClassDataActivityChanged(int transportType, boolean active, long tsNanos,
+ public void interfaceClassDataActivityChanged(int label, boolean active, long tsNanos,
int uid) {
// default no-op
}
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 51b720d..f9347ee 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -324,7 +324,7 @@
# key 365 "KEY_EPG"
key 366 DVR
# key 367 "KEY_MHP"
-# key 368 "KEY_LANGUAGE"
+key 368 LANGUAGE_SWITCH
# key 369 "KEY_TITLE"
key 370 CAPTIONS
# key 371 "KEY_ANGLE"
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index feb914f..757e9f8 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -630,7 +630,6 @@
const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
ScopedLocalRef<jobject> audioDescriptor(env);
- gAudioPresentationFields.init(env);
ScopedLocalRef presentationsJObj(env, JAudioPresentationInfo::asJobject(
env, gAudioPresentationFields));
switch (mediaEvent.extraMetaData.getTag()) {
@@ -3731,6 +3730,7 @@
gFields.linearBlockInitID = env->GetMethodID(linearBlockClazz, "<init>", "()V");
gFields.linearBlockSetInternalStateID =
env->GetMethodID(linearBlockClazz, "setInternalStateLocked", "(JZ)V");
+ gAudioPresentationFields.init(env);
}
static void android_media_tv_Tuner_native_setup(JNIEnv *env, jobject thiz) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index fa8c1fb..0ffcc45 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -594,6 +594,16 @@
|| cachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO);
}
+ /**
+ * Check if the Bluetooth device is an active LE Audio device
+ *
+ * @param cachedDevice the CachedBluetoothDevice
+ * @return if the Bluetooth device is an active LE Audio device
+ */
+ public static boolean isActiveLeAudioDevice(CachedBluetoothDevice cachedDevice) {
+ return cachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO);
+ }
+
private static boolean isDeviceConnected(CachedBluetoothDevice cachedDevice) {
if (cachedDevice == null) {
return false;
diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp
index 050d1d5..3424085 100644
--- a/packages/SystemUI/compose/scene/Android.bp
+++ b/packages/SystemUI/compose/scene/Android.bp
@@ -21,12 +21,19 @@
default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
}
+filegroup {
+ name: "PlatformComposeSceneTransitionLayout-srcs",
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
+
android_library {
name: "PlatformComposeSceneTransitionLayout",
manifest: "AndroidManifest.xml",
srcs: [
- "src/**/*.kt",
+ ":PlatformComposeSceneTransitionLayout-srcs",
],
static_libs: [
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 6153e19..3b999e30 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -17,18 +17,14 @@
package com.android.compose.animation.scene
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.movableContentOf
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.Snapshot
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.isSpecified
@@ -39,6 +35,7 @@
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
@@ -46,6 +43,7 @@
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.ui.util.lerp
+import kotlinx.coroutines.launch
/** An element on screen, that can be composed in one or more scenes. */
internal class Element(val key: ElementKey) {
@@ -92,13 +90,20 @@
}
/** The target values of this element in a given scene. */
- class TargetValues {
+ class TargetValues(val scene: SceneKey) {
val lastValues = Values()
var targetSize by mutableStateOf(SizeUnspecified)
var targetOffset by mutableStateOf(Offset.Unspecified)
val sharedValues = SnapshotStateMap<ValueKey, SharedValue<*>>()
+
+ /**
+ * The attached [ElementNode] a Modifier.element() for a given element and scene. During
+ * composition, this set could have 0 to 2 elements. After composition and after all
+ * modifier nodes have been attached/detached, this set should contain exactly 1 element.
+ */
+ val nodes = mutableSetOf<ElementNode>()
}
/** A shared value of this element. */
@@ -125,50 +130,31 @@
layoutImpl: SceneTransitionLayoutImpl,
scene: Scene,
key: ElementKey,
-): Modifier = composed {
- val sceneValues = remember(scene, key) { Element.TargetValues() }
- val element =
- // Get the element associated to [key] if it was already composed in another scene,
- // otherwise create it and add it to our Map<ElementKey, Element>. This is done inside a
- // withoutReadObservation() because there is no need to recompose when that map is mutated.
- Snapshot.withoutReadObservation {
- val element =
- layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it }
- val previousValues = element.sceneValues[scene.key]
- if (previousValues == null) {
- element.sceneValues[scene.key] = sceneValues
- } else if (previousValues != sceneValues) {
- error("$key was composed multiple times in $scene")
- }
+): Modifier {
+ val element: Element
+ val sceneValues: Element.TargetValues
- element
- }
-
- DisposableEffect(scene, sceneValues, element) {
- onDispose {
- element.sceneValues.remove(scene.key)
-
- // This was the last scene this element was in, so remove it from the map.
- if (element.sceneValues.isEmpty()) {
- layoutImpl.elements.remove(element.key)
- }
- }
+ // Get the element associated to [key] if it was already composed in another scene,
+ // otherwise create it and add it to our Map<ElementKey, Element>. This is done inside a
+ // withoutReadObservation() because there is no need to recompose when that map is mutated.
+ Snapshot.withoutReadObservation {
+ element = layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it }
+ sceneValues =
+ element.sceneValues[scene.key]
+ ?: Element.TargetValues(scene.key).also { element.sceneValues[scene.key] = it }
}
- val drawScale by
- remember(layoutImpl, element, scene, sceneValues) {
- derivedStateOf { getDrawScale(layoutImpl, element, scene, sceneValues) }
- }
-
- drawWithContent {
+ return this.then(ElementModifier(layoutImpl, element, sceneValues))
+ .drawWithContent {
if (shouldDrawElement(layoutImpl, scene, element)) {
+ val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues)
if (drawScale == Scale.Default) {
- this@drawWithContent.drawContent()
+ drawContent()
} else {
scale(
drawScale.scaleX,
drawScale.scaleY,
- if (drawScale.pivot.isUnspecified) center else drawScale.pivot
+ if (drawScale.pivot.isUnspecified) center else drawScale.pivot,
) {
this@drawWithContent.drawContent()
}
@@ -186,6 +172,84 @@
.testTag(key.testTag)
}
+/**
+ * An element associated to [ElementNode]. Note that this element does not support updates as its
+ * arguments should always be the same.
+ */
+private data class ElementModifier(
+ private val layoutImpl: SceneTransitionLayoutImpl,
+ private val element: Element,
+ private val sceneValues: Element.TargetValues,
+) : ModifierNodeElement<ElementNode>() {
+ override fun create(): ElementNode = ElementNode(layoutImpl, element, sceneValues)
+
+ override fun update(node: ElementNode) {
+ node.update(layoutImpl, element, sceneValues)
+ }
+}
+
+internal class ElementNode(
+ layoutImpl: SceneTransitionLayoutImpl,
+ element: Element,
+ sceneValues: Element.TargetValues,
+) : Modifier.Node() {
+ private var layoutImpl: SceneTransitionLayoutImpl = layoutImpl
+ private var element: Element = element
+ private var sceneValues: Element.TargetValues = sceneValues
+
+ override fun onAttach() {
+ super.onAttach()
+ addNodeToSceneValues()
+ }
+
+ private fun addNodeToSceneValues() {
+ sceneValues.nodes.add(this)
+
+ coroutineScope.launch {
+ // At this point all [CodeLocationNode] have been attached or detached, which means that
+ // [sceneValues.codeLocations] should have exactly 1 element, otherwise this means that
+ // this element was composed multiple times in the same scene.
+ val nCodeLocations = sceneValues.nodes.size
+ if (nCodeLocations != 1 || !sceneValues.nodes.contains(this@ElementNode)) {
+ error("${element.key} was composed $nCodeLocations times in ${sceneValues.scene}")
+ }
+ }
+ }
+
+ override fun onDetach() {
+ super.onDetach()
+ removeNodeFromSceneValues()
+ }
+
+ private fun removeNodeFromSceneValues() {
+ sceneValues.nodes.remove(this)
+
+ // If element is not composed from this scene anymore, remove the scene values. This works
+ // because [onAttach] is called before [onDetach], so if an element is moved from the UI
+ // tree we will first add the new code location then remove the old one.
+ if (sceneValues.nodes.isEmpty()) {
+ element.sceneValues.remove(sceneValues.scene)
+ }
+
+ // If the element is not composed in any scene, remove it from the elements map.
+ if (element.sceneValues.isEmpty()) {
+ layoutImpl.elements.remove(element.key)
+ }
+ }
+
+ fun update(
+ layoutImpl: SceneTransitionLayoutImpl,
+ element: Element,
+ sceneValues: Element.TargetValues,
+ ) {
+ removeNodeFromSceneValues()
+ this.layoutImpl = layoutImpl
+ this.element = element
+ this.sceneValues = sceneValues
+ addNodeToSceneValues()
+ }
+}
+
private fun shouldDrawElement(
layoutImpl: SceneTransitionLayoutImpl,
scene: Scene,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index bc015ee..5b752eb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -43,7 +43,10 @@
name: String,
identity: Any = Object(),
) : Key(name, identity) {
- @VisibleForTesting val testTag: String = "scene:$name"
+ @VisibleForTesting
+ // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
+ // access internal members.
+ val testTag: String = "scene:$name"
/** The unique [ElementKey] identifying this scene's root element. */
val rootElementKey = ElementKey(name, identity)
@@ -64,7 +67,10 @@
*/
val isBackground: Boolean = false,
) : Key(name, identity), ElementMatcher {
- @VisibleForTesting val testTag: String = "element:$name"
+ @VisibleForTesting
+ // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
+ // access internal members.
+ val testTag: String = "element:$name"
override fun matches(key: ElementKey, scene: SceneKey): Boolean {
return key == this
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 1a79522..857a596 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -76,6 +76,8 @@
private val layoutImpl: SceneTransitionLayoutImpl,
private val scene: Scene,
) : SceneScope {
+ override val layoutState: SceneTransitionLayoutState = layoutImpl.state
+
override fun Modifier.element(key: ElementKey): Modifier {
return element(layoutImpl, scene, key)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 838cb3b..c51287a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -17,7 +17,6 @@
package com.android.compose.animation.scene
import android.util.Log
-import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
@@ -37,8 +36,7 @@
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
-@VisibleForTesting
-class SceneGestureHandler(
+internal class SceneGestureHandler(
internal val layoutImpl: SceneTransitionLayoutImpl,
internal val orientation: Orientation,
private val coroutineScope: CoroutineScope,
@@ -63,12 +61,10 @@
internal val currentScene: Scene
get() = layoutImpl.scene(transitionState.currentScene)
- @VisibleForTesting
- val isDrivingTransition
+ internal val isDrivingTransition
get() = transitionState == swipeTransition
- @VisibleForTesting
- var isAnimatingOffset
+ internal var isAnimatingOffset
get() = swipeTransition.isAnimatingOffset
private set(value) {
swipeTransition.isAnimatingOffset = value
@@ -81,7 +77,7 @@
* The velocity threshold at which the intent of the user is to swipe up or down. It is the same
* as SwipeableV2Defaults.VelocityThreshold.
*/
- @VisibleForTesting val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() }
+ internal val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() }
/**
* The positional threshold at which the intent of the user is to swipe to the next scene. It is
@@ -533,8 +529,7 @@
}
}
-@VisibleForTesting
-class SceneNestedScrollHandler(
+internal class SceneNestedScrollHandler(
private val gestureHandler: SceneGestureHandler,
private val startBehavior: NestedScrollBehavior,
private val endBehavior: NestedScrollBehavior,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 9c31445..30d13df 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -57,30 +57,17 @@
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
- val density = LocalDensity.current
- val coroutineScope = rememberCoroutineScope()
- val layoutImpl = remember {
- SceneTransitionLayoutImpl(
- onChangeScene = onChangeScene,
- builder = scenes,
- transitions = transitions,
- state = state,
- density = density,
- edgeDetector = edgeDetector,
- transitionInterceptionThreshold = transitionInterceptionThreshold,
- coroutineScope = coroutineScope,
- )
- }
-
- layoutImpl.onChangeScene = onChangeScene
- layoutImpl.transitions = transitions
- layoutImpl.density = density
- layoutImpl.edgeDetector = edgeDetector
- layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
-
- layoutImpl.setScenes(scenes)
- layoutImpl.setCurrentScene(currentScene)
- layoutImpl.Content(modifier)
+ SceneTransitionLayoutForTesting(
+ currentScene,
+ onChangeScene,
+ transitions,
+ state,
+ edgeDetector,
+ transitionInterceptionThreshold,
+ modifier,
+ onLayoutImpl = null,
+ scenes,
+ )
}
interface SceneTransitionLayoutScope {
@@ -108,6 +95,9 @@
@ElementDsl
interface SceneScope {
+ /** The state of the [SceneTransitionLayout] in which this scene is contained. */
+ val layoutState: SceneTransitionLayoutState
+
/**
* Tag an element identified by [key].
*
@@ -228,3 +218,47 @@
Left(Orientation.Horizontal),
Right(Orientation.Horizontal),
}
+
+/**
+ * An internal version of [SceneTransitionLayout] to be used for tests.
+ *
+ * Important: You should use this only in tests and if you need to access the underlying
+ * [SceneTransitionLayoutImpl]. In other cases, you should use [SceneTransitionLayout].
+ */
+@Composable
+internal fun SceneTransitionLayoutForTesting(
+ currentScene: SceneKey,
+ onChangeScene: (SceneKey) -> Unit,
+ transitions: SceneTransitions,
+ state: SceneTransitionLayoutState,
+ edgeDetector: EdgeDetector,
+ transitionInterceptionThreshold: Float,
+ modifier: Modifier,
+ onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)?,
+ scenes: SceneTransitionLayoutScope.() -> Unit,
+) {
+ val density = LocalDensity.current
+ val coroutineScope = rememberCoroutineScope()
+ val layoutImpl = remember {
+ SceneTransitionLayoutImpl(
+ onChangeScene = onChangeScene,
+ builder = scenes,
+ transitions = transitions,
+ state = state,
+ density = density,
+ edgeDetector = edgeDetector,
+ transitionInterceptionThreshold = transitionInterceptionThreshold,
+ coroutineScope = coroutineScope,
+ )
+ .also { onLayoutImpl?.invoke(it) }
+ }
+
+ layoutImpl.onChangeScene = onChangeScene
+ layoutImpl.transitions = transitions
+ layoutImpl.density = density
+ layoutImpl.edgeDetector = edgeDetector
+
+ layoutImpl.setScenes(scenes)
+ layoutImpl.setCurrentScene(currentScene)
+ layoutImpl.Content(modifier)
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 94f2737..60f385a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -17,7 +17,6 @@
package com.android.compose.animation.scene
import androidx.activity.compose.BackHandler
-import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
@@ -42,8 +41,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
-@VisibleForTesting
-class SceneTransitionLayoutImpl(
+internal class SceneTransitionLayoutImpl(
onChangeScene: (SceneKey) -> Unit,
builder: SceneTransitionLayoutScope.() -> Unit,
transitions: SceneTransitions,
@@ -260,8 +258,7 @@
internal fun isSceneReady(scene: SceneKey): Boolean = readyScenes.containsKey(scene)
- @VisibleForTesting
- fun setScenesTargetSizeForTest(size: IntSize) {
+ internal fun setScenesTargetSizeForTest(size: IntSize) {
scenes.values.forEach { it.targetSize = size }
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index b9f83c5..64c9775 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -30,6 +30,22 @@
*/
var transitionState: TransitionState by mutableStateOf(TransitionState.Idle(initialScene))
internal set
+
+ /**
+ * Whether we are transitioning, optionally restricting the check to the transition between
+ * [from] and [to].
+ */
+ fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
+ val transition = transitionState as? TransitionState.Transition ?: return false
+
+ // TODO(b/310915136): Remove this check.
+ if (transition.fromScene == transition.toScene) {
+ return false
+ }
+
+ return (from == null || transition.fromScene == from) &&
+ (to == null || transition.toScene == to)
+ }
}
sealed interface TransitionState {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 72a2d61..2172ed3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -16,7 +16,6 @@
package com.android.compose.animation.scene
-import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.snap
import androidx.compose.ui.geometry.Offset
@@ -38,12 +37,11 @@
/** The transitions configuration of a [SceneTransitionLayout]. */
class SceneTransitions(
- @get:VisibleForTesting val transitionSpecs: List<TransitionSpec>,
+ internal val transitionSpecs: List<TransitionSpec>,
) {
private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
- @VisibleForTesting
- fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+ internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) }
}
diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp
index 6de7550..13df35b 100644
--- a/packages/SystemUI/compose/scene/tests/Android.bp
+++ b/packages/SystemUI/compose/scene/tests/Android.bp
@@ -30,10 +30,13 @@
srcs: [
"src/**/*.kt",
+
+ // TODO(b/240432457): Depend on PlatformComposeSceneTransitionLayout
+ // directly once Kotlin tests can access internal declarations.
+ ":PlatformComposeSceneTransitionLayout-srcs",
],
static_libs: [
- "PlatformComposeSceneTransitionLayout",
"PlatformComposeSceneTransitionLayoutTestsUtils",
"androidx.test.runner",
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 6401bb3..cc7a0b8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -18,9 +18,15 @@
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.intermediateLayout
@@ -29,6 +35,7 @@
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -209,4 +216,215 @@
}
}
}
+
+ @Test
+ fun elementIsReusedInSameSceneAndBetweenScenes() {
+ var currentScene by mutableStateOf(TestScenes.SceneA)
+ var sceneCState by mutableStateOf(0)
+ var sceneDState by mutableStateOf(0)
+ val key = TestElements.Foo
+ var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
+
+ rule.setContent {
+ SceneTransitionLayoutForTesting(
+ currentScene = currentScene,
+ onChangeScene = { currentScene = it },
+ transitions = remember { transitions {} },
+ state = remember { SceneTransitionLayoutState(currentScene) },
+ edgeDetector = DefaultEdgeDetector,
+ modifier = Modifier,
+ transitionInterceptionThreshold = 0f,
+ onLayoutImpl = { nullableLayoutImpl = it },
+ ) {
+ scene(TestScenes.SceneA) { /* Nothing */}
+ scene(TestScenes.SceneB) { Box(Modifier.element(key)) }
+ scene(TestScenes.SceneC) {
+ when (sceneCState) {
+ 0 -> Row(Modifier.element(key)) {}
+ 1 -> Column(Modifier.element(key)) {}
+ else -> {
+ /* Nothing */
+ }
+ }
+ }
+ scene(TestScenes.SceneD) {
+ // We should be able to extract the modifier before assigning it to different
+ // nodes.
+ val childModifier = Modifier.element(key)
+ when (sceneDState) {
+ 0 -> Row(childModifier) {}
+ 1 -> Column(childModifier) {}
+ else -> {
+ /* Nothing */
+ }
+ }
+ }
+ }
+ }
+
+ assertThat(nullableLayoutImpl).isNotNull()
+ val layoutImpl = nullableLayoutImpl!!
+
+ // Scene A: no elements in the elements map.
+ rule.waitForIdle()
+ assertThat(layoutImpl.elements).isEmpty()
+
+ // Scene B: element is in the map.
+ currentScene = TestScenes.SceneB
+ rule.waitForIdle()
+
+ assertThat(layoutImpl.elements.keys).containsExactly(key)
+ val element = layoutImpl.elements.getValue(key)
+ assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneB)
+
+ // Scene C, state 0: the same element is reused.
+ currentScene = TestScenes.SceneC
+ sceneCState = 0
+ rule.waitForIdle()
+
+ assertThat(layoutImpl.elements.keys).containsExactly(key)
+ assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
+ assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneC)
+
+ // Scene C, state 1: the same element is reused.
+ sceneCState = 1
+ rule.waitForIdle()
+
+ assertThat(layoutImpl.elements.keys).containsExactly(key)
+ assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
+ assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneC)
+
+ // Scene D, state 0: the same element is reused.
+ currentScene = TestScenes.SceneD
+ sceneDState = 0
+ rule.waitForIdle()
+
+ assertThat(layoutImpl.elements.keys).containsExactly(key)
+ assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
+ assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneD)
+
+ // Scene D, state 1: the same element is reused.
+ sceneDState = 1
+ rule.waitForIdle()
+
+ assertThat(layoutImpl.elements.keys).containsExactly(key)
+ assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
+ assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneD)
+
+ // Scene D, state 2: the element is removed from the map.
+ sceneDState = 2
+ rule.waitForIdle()
+
+ assertThat(element.sceneValues).isEmpty()
+ assertThat(layoutImpl.elements).isEmpty()
+ }
+
+ @Test
+ fun throwsExceptionWhenElementIsComposedMultipleTimes() {
+ val key = TestElements.Foo
+
+ assertThrows(IllegalStateException::class.java) {
+ rule.setContent {
+ TestSceneScope {
+ Column {
+ Box(Modifier.element(key))
+ Box(Modifier.element(key))
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun throwsExceptionWhenElementIsComposedMultipleTimes_childModifier() {
+ val key = TestElements.Foo
+
+ assertThrows(IllegalStateException::class.java) {
+ rule.setContent {
+ TestSceneScope {
+ Column {
+ val childModifier = Modifier.element(key)
+ Box(childModifier)
+ Box(childModifier)
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun throwsExceptionWhenElementIsComposedMultipleTimes_childModifier_laterDuplication() {
+ val key = TestElements.Foo
+
+ assertThrows(IllegalStateException::class.java) {
+ var nElements by mutableStateOf(1)
+ rule.setContent {
+ TestSceneScope {
+ Column {
+ val childModifier = Modifier.element(key)
+ repeat(nElements) { Box(childModifier) }
+ }
+ }
+ }
+
+ nElements = 2
+ rule.waitForIdle()
+ }
+ }
+
+ @Test
+ fun throwsExceptionWhenElementIsComposedMultipleTimes_updatedNode() {
+ assertThrows(IllegalStateException::class.java) {
+ var key by mutableStateOf(TestElements.Foo)
+ rule.setContent {
+ TestSceneScope {
+ Column {
+ Box(Modifier.element(key))
+ Box(Modifier.element(TestElements.Bar))
+ }
+ }
+ }
+
+ key = TestElements.Bar
+ rule.waitForIdle()
+ }
+ }
+
+ @Test
+ fun elementModifierSupportsUpdates() {
+ var key by mutableStateOf(TestElements.Foo)
+ var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
+
+ rule.setContent {
+ SceneTransitionLayoutForTesting(
+ currentScene = TestScenes.SceneA,
+ onChangeScene = {},
+ transitions = remember { transitions {} },
+ state = remember { SceneTransitionLayoutState(TestScenes.SceneA) },
+ edgeDetector = DefaultEdgeDetector,
+ modifier = Modifier,
+ transitionInterceptionThreshold = 0f,
+ onLayoutImpl = { nullableLayoutImpl = it },
+ ) {
+ scene(TestScenes.SceneA) { Box(Modifier.element(key)) }
+ }
+ }
+
+ assertThat(nullableLayoutImpl).isNotNull()
+ val layoutImpl = nullableLayoutImpl!!
+
+ // There is only Foo in the elements map.
+ assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo)
+ val fooElement = layoutImpl.elements.getValue(TestElements.Foo)
+ assertThat(fooElement.sceneValues.keys).containsExactly(TestScenes.SceneA)
+
+ key = TestElements.Bar
+
+ // There is only Bar in the elements map and foo scene values was cleaned up.
+ rule.waitForIdle()
+ assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Bar)
+ val barElement = layoutImpl.elements.getValue(TestElements.Bar)
+ assertThat(barElement.sceneValues.keys).containsExactly(TestScenes.SceneA)
+ assertThat(fooElement.sceneValues).isEmpty()
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
new file mode 100644
index 0000000..94c51ca
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SceneTransitionLayoutStateTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun isTransitioningTo_idle() {
+ val state = SceneTransitionLayoutState(TestScenes.SceneA)
+
+ assertThat(state.isTransitioning()).isFalse()
+ assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
+ assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
+ assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
+ .isFalse()
+ }
+
+ @Test
+ fun isTransitioningTo_fromSceneEqualToToScene() {
+ val state = SceneTransitionLayoutState(TestScenes.SceneA)
+ state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneA)
+
+ assertThat(state.isTransitioning()).isFalse()
+ assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
+ assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
+ assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
+ .isFalse()
+ }
+
+ @Test
+ fun isTransitioningTo_transition() {
+ val state = SceneTransitionLayoutState(TestScenes.SceneA)
+ state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneB)
+
+ assertThat(state.isTransitioning()).isTrue()
+ assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
+ assertThat(state.isTransitioning(from = TestScenes.SceneB)).isFalse()
+ assertThat(state.isTransitioning(to = TestScenes.SceneB)).isTrue()
+ assertThat(state.isTransitioning(to = TestScenes.SceneA)).isFalse()
+ assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+ }
+
+ private fun transition(from: SceneKey, to: SceneKey): TransitionState.Transition {
+ return object : TransitionState.Transition {
+ override val currentScene: SceneKey = from
+ override val fromScene: SceneKey = from
+ override val toScene: SceneKey = to
+ override val progress: Float = 0f
+ override val isInitiatedByUserInput: Boolean = false
+ override val isUserInputOngoing: Boolean = false
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
index b4c393e..b83705a 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
@@ -26,6 +26,7 @@
val SceneA = SceneKey("SceneA")
val SceneB = SceneKey("SceneB")
val SceneC = SceneKey("SceneC")
+ val SceneD = SceneKey("SceneD")
}
/** Element keys that can be reused by tests. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 4175937..b064391 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -83,6 +83,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
@@ -170,6 +171,7 @@
@NonNull private final SelectedUserInteractor mSelectedUserInteractor;
@NonNull private final FpsUnlockTracker mFpsUnlockTracker;
private final boolean mIgnoreRefreshRate;
+ private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -283,8 +285,8 @@
mPrimaryBouncerInteractor,
mAlternateBouncerInteractor,
mUdfpsKeyguardAccessibilityDelegate,
- mUdfpsKeyguardViewModels,
- mSelectedUserInteractor
+ mKeyguardTransitionInteractor,
+ mSelectedUserInteractor
)));
}
@@ -649,7 +651,8 @@
@NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
@NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider,
@NonNull SelectedUserInteractor selectedUserInteractor,
- @NonNull FpsUnlockTracker fpsUnlockTracker) {
+ @NonNull FpsUnlockTracker fpsUnlockTracker,
+ @NonNull KeyguardTransitionInteractor keyguardTransitionInteractor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -695,6 +698,7 @@
mSelectedUserInteractor = selectedUserInteractor;
mFpsUnlockTracker = fpsUnlockTracker;
mFpsUnlockTracker.startTracking();
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mTouchProcessor = singlePointerTouchProcessor;
mSessionTracker = sessionTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 934f9f9..8f31a2d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -51,8 +51,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -63,7 +63,6 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import javax.inject.Provider
private const val TAG = "UdfpsControllerOverlay"
@@ -102,7 +101,7 @@
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
- private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
+ private val transitionInteractor: KeyguardTransitionInteractor,
private val selectedUserInteractor: SelectedUserInteractor,
) {
/** The view, when [isShowing], or null. */
@@ -264,11 +263,11 @@
dialogManager,
controller,
activityLaunchAnimator,
- featureFlags,
primaryBouncerInteractor,
alternateBouncerInteractor,
udfpsKeyguardAccessibilityDelegate,
selectedUserInteractor,
+ transitionInteractor,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index d7df0e5..2c4ed58 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -16,7 +16,6 @@
package com.android.systemui.biometrics
-import android.animation.ValueAnimator
import android.content.res.Configuration
import android.util.MathUtils
import android.view.View
@@ -27,17 +26,17 @@
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI
@@ -48,10 +47,14 @@
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import java.io.PrintWriter
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.launch
/** Class that coordinates non-HBM animations during keyguard authentication. */
+@ExperimentalCoroutinesApi
open class UdfpsKeyguardViewControllerLegacy(
private val view: UdfpsKeyguardViewLegacy,
statusBarStateController: StatusBarStateController,
@@ -65,11 +68,11 @@
systemUIDialogManager: SystemUIDialogManager,
private val udfpsController: UdfpsController,
private val activityLaunchAnimator: ActivityLaunchAnimator,
- featureFlags: FeatureFlags,
primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
private val selectedUserInteractor: SelectedUserInteractor,
+ private val transitionInteractor: KeyguardTransitionInteractor,
) :
UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
view,
@@ -91,44 +94,10 @@
private var launchTransitionFadingAway = false
private var isLaunchingActivity = false
private var activityLaunchProgress = 0f
- private val unlockedScreenOffDozeAnimator =
- ValueAnimator.ofFloat(0f, 1f).apply {
- duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong()
- interpolator = Interpolators.ALPHA_IN
- addUpdateListener { animation ->
- view.onDozeAmountChanged(
- animation.animatedFraction,
- animation.animatedValue as Float,
- UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF
- )
- }
- }
private var inputBouncerExpansion = 0f
private val stateListener: StatusBarStateController.StateListener =
object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- if (lastDozeAmount < linear) {
- showUdfpsBouncer(false)
- }
- unlockedScreenOffDozeAnimator.cancel()
- val animatingFromUnlockedScreenOff =
- unlockedScreenOffAnimationController.isAnimationPlaying()
- if (animatingFromUnlockedScreenOff && linear != 0f) {
- // we manually animate the fade in of the UDFPS icon since the unlocked
- // screen off animation prevents the doze amounts to be incrementally eased in
- unlockedScreenOffDozeAnimator.start()
- } else {
- view.onDozeAmountChanged(
- linear,
- eased,
- UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
- )
- }
- lastDozeAmount = linear
- updatePauseAuth()
- }
-
override fun onStateChanged(statusBarState: Int) {
this@UdfpsKeyguardViewControllerLegacy.statusBarState = statusBarState
updateAlpha()
@@ -222,11 +191,39 @@
repeatOnLifecycle(Lifecycle.State.CREATED) {
listenForBouncerExpansion(this)
listenForAlternateBouncerVisibility(this)
+ listenForGoneToAodTransition(this)
+ listenForLockscreenAodTransitions(this)
}
}
}
@VisibleForTesting
+ suspend fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
+ return scope.launch {
+ transitionInteractor.goneToAodTransition.collect { transitionStep ->
+ view.onDozeAmountChanged(
+ transitionStep.value,
+ transitionStep.value,
+ ANIMATION_UNLOCKED_SCREEN_OFF,
+ )
+ }
+ }
+ }
+
+ @VisibleForTesting
+ suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job {
+ return scope.launch {
+ transitionInteractor.dozeAmountTransition.collect { transitionStep ->
+ view.onDozeAmountChanged(
+ transitionStep.value,
+ transitionStep.value,
+ UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
+ )
+ }
+ }
+ }
+
+ @VisibleForTesting
override suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
return scope.launch {
primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
index 95e3a76..f4ed8ce 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
@@ -73,9 +73,6 @@
// AOD anti-burn-in offsets
private final int mMaxBurnInOffsetX;
private final int mMaxBurnInOffsetY;
- private float mBurnInOffsetX;
- private float mBurnInOffsetY;
- private float mBurnInProgress;
private float mInterpolatedDarkAmount;
private int mAnimationType = ANIMATION_NONE;
private boolean mFullyInflated;
@@ -138,20 +135,22 @@
// AoD-burn in location, else we need to translate the icon from LS => AoD.
final float darkAmountForAnimation = mAnimationType == ANIMATION_UNLOCKED_SCREEN_OFF
? 1f : mInterpolatedDarkAmount;
- mBurnInOffsetX = MathUtils.lerp(0f,
+ final float burnInOffsetX = MathUtils.lerp(0f,
getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
- mMaxBurnInOffsetX, darkAmountForAnimation);
- mBurnInOffsetY = MathUtils.lerp(0f,
+ final float burnInOffsetY = MathUtils.lerp(0f,
getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
- mMaxBurnInOffsetY, darkAmountForAnimation);
- mBurnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), darkAmountForAnimation);
+ final float burnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(),
+ darkAmountForAnimation);
if (mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN && !mPauseAuth) {
- mLockScreenFp.setTranslationX(mBurnInOffsetX);
- mLockScreenFp.setTranslationY(mBurnInOffsetY);
+ mLockScreenFp.setTranslationX(burnInOffsetX);
+ mLockScreenFp.setTranslationY(burnInOffsetY);
mBgProtection.setAlpha(1f - mInterpolatedDarkAmount);
mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount);
} else if (darkAmountForAnimation == 0f) {
+ // we're on the lockscreen and should use mAlpha (changes based on shade expansion)
mLockScreenFp.setTranslationX(0);
mLockScreenFp.setTranslationY(0);
mBgProtection.setAlpha(mAlpha / 255f);
@@ -162,9 +161,9 @@
}
mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount);
- mAodFp.setTranslationX(mBurnInOffsetX);
- mAodFp.setTranslationY(mBurnInOffsetY);
- mAodFp.setProgress(mBurnInProgress);
+ mAodFp.setTranslationX(burnInOffsetX);
+ mAodFp.setTranslationY(burnInOffsetY);
+ mAodFp.setProgress(burnInProgress);
mAodFp.setAlpha(mInterpolatedDarkAmount);
// done animating
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 78f2da5..789a1e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -279,6 +279,11 @@
}
@Override
+ public void onNullBinding(ComponentName name) {
+ executeSetBindService(false);
+ }
+
+ @Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
handleDeath();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index c5f16aa..5f0d4d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -43,7 +43,7 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -68,7 +68,6 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
-import javax.inject.Provider
import org.mockito.Mockito.`when` as whenever
private const val REQUEST_ID = 2L
@@ -111,11 +110,10 @@
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
- @Mock private lateinit var udfpsUtils: UdfpsUtils
@Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
UdfpsKeyguardAccessibilityDelegate
- @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -164,7 +162,7 @@
alternateBouncerInteractor,
isDebuggable,
udfpsKeyguardAccessibilityDelegate,
- udfpsKeyguardViewModels,
+ keyguardTransitionInteractor,
mSelectedUserInteractor,
)
block()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 675ca63..c8c400d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -86,6 +86,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
@@ -237,6 +238,8 @@
private ViewRootImpl mViewRootImpl;
@Mock
private FpsUnlockTracker mFpsUnlockTracker;
+ @Mock
+ private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Before
public void setUp() {
@@ -329,7 +332,8 @@
mUdfpsKeyguardAccessibilityDelegate,
mUdfpsKeyguardViewModels,
mSelectedUserInteractor,
- mFpsUnlockTracker
+ mFpsUnlockTracker,
+ mKeyguardTransitionInteractor
);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 2c4e136..2ea803c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -31,6 +31,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -71,6 +72,7 @@
protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
protected @Mock SelectedUserInteractor mSelectedUserInteractor;
+ protected @Mock KeyguardTransitionInteractor mKeyguardTransitionInteractor;
protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@@ -141,11 +143,11 @@
mDialogManager,
mUdfpsController,
mActivityLaunchAnimator,
- mFeatureFlags,
mPrimaryBouncerInteractor,
mAlternateBouncerInteractor,
mUdfpsKeyguardAccessibilityDelegate,
- mSelectedUserInteractor);
+ mSelectedUserInteractor,
+ mKeyguardTransitionInteractor);
return controller;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
index 21928cd..98d8b05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
@@ -66,17 +66,12 @@
public void testViewControllerQueriesSBStateOnAttached() {
mController.onViewAttached();
verify(mStatusBarStateController).getState();
- verify(mStatusBarStateController).getDozeAmount();
- final float dozeAmount = .88f;
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
- when(mStatusBarStateController.getDozeAmount()).thenReturn(dozeAmount);
captureStatusBarStateListeners();
mController.onViewAttached();
verify(mView, atLeast(1)).setPauseAuth(true);
- verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount,
- UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
}
@Test
@@ -91,19 +86,6 @@
}
@Test
- public void testDozeEventsSentToView() {
- mController.onViewAttached();
- captureStatusBarStateListeners();
-
- final float linear = .55f;
- final float eased = .65f;
- mStatusBarStateListener.onDozeAmountChanged(linear, eased);
-
- verify(mView).onDozeAmountChanged(linear, eased,
- UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
- }
-
- @Test
public void testShouldPauseAuthUnpausedAlpha0() {
mController.onViewAttached();
captureStatusBarStateListeners();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 97dada2..a49150f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -31,7 +31,12 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
@@ -49,6 +54,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.mock
@@ -65,6 +71,7 @@
private val testScope = TestScope(testDispatcher)
private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
@Mock private lateinit var bouncerLogger: TableLogBuffer
@@ -78,6 +85,7 @@
testScope.backgroundScope,
bouncerLogger,
)
+ transitionRepository = FakeKeyguardTransitionRepository()
super.setUp()
}
@@ -107,6 +115,12 @@
mock(SystemClock::class.java),
mKeyguardUpdateMonitor,
)
+ mKeyguardTransitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ )
+ .keyguardTransitionInteractor
return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
}
@@ -258,4 +272,145 @@
job.cancel()
}
+
+ @Test
+ fun aodToLockscreen_dozeAmountChanged() =
+ testScope.runTest {
+ // GIVEN view is attached
+ mController.onViewAttached()
+ Mockito.reset(mView)
+
+ val job = mController.listenForLockscreenAodTransitions(this)
+
+ // WHEN transitioning from lockscreen to aod
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = .3f,
+ transitionState = TransitionState.RUNNING
+ )
+ )
+ runCurrent()
+ // THEN doze amount is updated
+ verify(mView)
+ .onDozeAmountChanged(
+ eq(.3f),
+ eq(.3f),
+ eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED
+ )
+ )
+ runCurrent()
+ // THEN doze amount is updated
+ verify(mView)
+ .onDozeAmountChanged(
+ eq(1f),
+ eq(1f),
+ eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun lockscreenToAod_dozeAmountChanged() =
+ testScope.runTest {
+ // GIVEN view is attached
+ mController.onViewAttached()
+ Mockito.reset(mView)
+
+ val job = mController.listenForLockscreenAodTransitions(this)
+
+ // WHEN transitioning from lockscreen to aod
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = .3f,
+ transitionState = TransitionState.RUNNING
+ )
+ )
+ runCurrent()
+ // THEN doze amount is updated
+ verify(mView)
+ .onDozeAmountChanged(
+ eq(.3f),
+ eq(.3f),
+ eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED
+ )
+ )
+ runCurrent()
+ // THEN doze amount is updated
+ verify(mView)
+ .onDozeAmountChanged(
+ eq(1f),
+ eq(1f),
+ eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun goneToAod_dozeAmountChanged() =
+ testScope.runTest {
+ // GIVEN view is attached
+ mController.onViewAttached()
+ Mockito.reset(mView)
+
+ val job = mController.listenForGoneToAodTransition(this)
+
+ // WHEN transitioning from lockscreen to aod
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = .3f,
+ transitionState = TransitionState.RUNNING
+ )
+ )
+ runCurrent()
+ // THEN doze amount is updated
+ verify(mView)
+ .onDozeAmountChanged(
+ eq(.3f),
+ eq(.3f),
+ eq(UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF)
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED
+ )
+ )
+ runCurrent()
+ // THEN doze amount is updated
+ verify(mView)
+ .onDozeAmountChanged(
+ eq(1f),
+ eq(1f),
+ eq(UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF)
+ )
+
+ job.cancel()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 3b07913..5245b22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -27,7 +27,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -41,7 +40,6 @@
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.SparseArray;
import android.view.View;
@@ -82,7 +80,6 @@
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -107,10 +104,6 @@
ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS");
private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE);
private static final String SETTING = QSHost.TILES_SETTING;
-
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
@Mock
private PluginManager mPluginManager;
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 6cc52d7..fbd63c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -403,6 +403,31 @@
verify(falseContext).bindServiceAsUser(any(), any(), eq(flags), any());
}
+ @Test
+ public void testNullBindingCallsUnbind() {
+ Context mockContext = mock(Context.class);
+ // Binding has to succeed
+ when(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+ TileLifecycleManager manager = new TileLifecycleManager(mHandler, mockContext,
+ mock(IQSService.class),
+ mMockPackageManagerAdapter,
+ mMockBroadcastDispatcher,
+ mTileServiceIntent,
+ mUser,
+ mActivityManager,
+ mExecutor);
+
+ manager.executeSetBindService(true);
+ mExecutor.runAllReady();
+
+ ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+ verify(mockContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any());
+
+ captor.getValue().onNullBinding(mTileServiceComponentName);
+ mExecutor.runAllReady();
+ verify(mockContext).unbindService(captor.getValue());
+ }
+
private void mockChangeEnabled(long changeId, boolean enabled) {
doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
any(UserHandle.class)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 355ca81..8c896a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -21,7 +21,6 @@
import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserHandle
-import android.platform.test.flag.junit.SetFlagsRule
import android.service.quicksettings.Tile
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -62,7 +61,6 @@
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyString
@@ -77,8 +75,6 @@
@OptIn(ExperimentalCoroutinesApi::class)
class CurrentTilesInteractorImplTest : SysuiTestCase() {
- @Rule @JvmField val setFlagsRule = SetFlagsRule()
-
private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository()
private val userRepository = FakeUserRepository()
private val installedTilesPackageRepository = FakeInstalledTilesComponentRepository()
@@ -109,7 +105,7 @@
fun setup() {
MockitoAnnotations.initMocks(this)
- setFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
+ mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
// TODO(b/299909337): Add test checking the new factory is used when the flag is on
featureFlags.set(Flags.QS_PIPELINE_NEW_TILES, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
index 62ca965..2e63708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
@@ -1,13 +1,11 @@
package com.android.systemui.qs.pipeline.shared
-import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -15,22 +13,20 @@
@RunWith(AndroidJUnit4::class)
class QSPipelineFlagsRepositoryTest : SysuiTestCase() {
- @Rule @JvmField val setFlagsRule = SetFlagsRule()
-
private val fakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
private val underTest = QSPipelineFlagsRepository(fakeFeatureFlagsClassic)
@Test
fun pipelineFlagDisabled() {
- setFlagsRule.disableFlags(Flags.FLAG_QS_NEW_PIPELINE)
+ mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_PIPELINE)
assertThat(underTest.pipelineEnabled).isFalse()
}
@Test
fun pipelineFlagEnabled() {
- setFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
+ mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
assertThat(underTest.pipelineEnabled).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 1d8a346..84cd518 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE;
import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
import static com.android.systemui.statusbar.phone.AutoTileManager.DEVICE_CONTROLS;
@@ -135,6 +136,8 @@
MockitoAnnotations.initMocks(this);
mSecureSettings = new FakeSettings();
+ mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE);
+
mContext.getOrCreateTestableResources().addOverride(
R.array.config_quickSettingsAutoAdd,
new String[] {
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index fc4ed1d..b9e34ee 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -33,3 +33,10 @@
],
visibility: ["//visibility:public"],
}
+
+java_host_for_device {
+ name: "core-xml-for-device",
+ libs: [
+ "core-xml-for-host",
+ ],
+}
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 692d598..3e54c7a 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -103,7 +103,21 @@
# Containers
class android.os.BaseBundle stubclass
class android.os.Bundle stubclass
+class android.os.PersistableBundle stubclass
# Misc
class android.os.PatternMatcher stubclass
class android.os.ParcelUuid stubclass
+
+# XML
+class com.android.internal.util.XmlPullParserWrapper stubclass
+class com.android.internal.util.XmlSerializerWrapper stubclass
+class com.android.internal.util.XmlUtils stubclass
+
+class com.android.modules.utils.BinaryXmlPullParser stubclass
+class com.android.modules.utils.BinaryXmlSerializer stubclass
+class com.android.modules.utils.FastDataInput stubclass
+class com.android.modules.utils.FastDataOutput stubclass
+class com.android.modules.utils.ModifiedUtf8 stubclass
+class com.android.modules.utils.TypedXmlPullParser stubclass
+class com.android.modules.utils.TypedXmlSerializer stubclass
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 776a19a..1ac6bf0 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -2,8 +2,11 @@
com.android.internal.util.ArrayUtils
+android.util.Xml
+
android.os.Binder
android.os.Binder$IdentitySupplier
android.os.IBinder
android.os.Process
android.os.SystemClock
+android.os.UserHandle
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index f9fc4d4..36356bd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -111,6 +111,7 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.ParseUtils;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.NetworkCapabilitiesUtils;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
@@ -475,7 +476,12 @@
ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
try {
- nms.registerObserver(mActivityChangeObserver);
+ if (!SdkLevel.isAtLeastV()) {
+ // On V+ devices, ConnectivityService calls BatteryStats API to update
+ // RadioPowerState change. So BatteryStatsService registers the callback only on
+ // pre V devices.
+ nms.registerObserver(mActivityChangeObserver);
+ }
cm.registerDefaultNetworkCallback(mNetworkCallback);
} catch (RemoteException e) {
Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index a0beedb..b99de5c 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -71,7 +71,7 @@
import com.android.server.display.config.RefreshRateZone;
import com.android.server.display.config.SdrHdrRatioMap;
import com.android.server.display.config.SdrHdrRatioPoint;
-import com.android.server.display.config.SensorDetails;
+import com.android.server.display.config.SensorData;
import com.android.server.display.config.ThermalStatus;
import com.android.server.display.config.ThermalThrottling;
import com.android.server.display.config.ThresholdPoint;
@@ -349,6 +349,20 @@
* <proxSensor>
* <type>android.sensor.proximity</type>
* <name>1234 Proximity Sensor</name>
+ * <refreshRate>
+ * <minimum>60</minimum>
+ * <maximum>60</maximum>
+ * </refreshRate>
+ * <supportedModes>
+ * <point>
+ * <first>60</first> // refreshRate
+ * <second>60</second> //vsyncRate
+ * </point>
+ * <point>
+ * <first>120</first> // refreshRate
+ * <second>120</second> //vsyncRate
+ * </point>
+ * </supportedModes>
* </proxSensor>
*
* <ambientLightHorizonLong>10001</ambientLightHorizonLong>
@@ -581,15 +595,15 @@
private final Context mContext;
// The details of the ambient light sensor associated with this display.
- private final SensorData mAmbientLightSensor = new SensorData();
+ private SensorData mAmbientLightSensor;
// The details of the doze brightness sensor associated with this display.
- private final SensorData mScreenOffBrightnessSensor = new SensorData();
+ private SensorData mScreenOffBrightnessSensor;
// The details of the proximity sensor associated with this display.
// Is null when no sensor should be used for that display
@Nullable
- private SensorData mProximitySensor = new SensorData();
+ private SensorData mProximitySensor;
private final List<RefreshRateLimitation> mRefreshRateLimitations =
new ArrayList<>(2 /*initialCapacity*/);
@@ -1913,9 +1927,10 @@
loadLuxThrottling(config);
loadQuirks(config);
loadBrightnessRamps(config);
- loadAmbientLightSensorFromDdc(config);
- loadScreenOffBrightnessSensorFromDdc(config);
- loadProxSensorFromDdc(config);
+ mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(config,
+ mContext.getResources());
+ mScreenOffBrightnessSensor = SensorData.loadScreenOffBrightnessSensorConfig(config);
+ mProximitySensor = SensorData.loadProxSensorConfig(config);
loadAmbientHorizonFromDdc(config);
loadBrightnessChangeThresholds(config);
loadAutoBrightnessConfigValues(config);
@@ -1940,9 +1955,9 @@
loadBrightnessConstraintsFromConfigXml();
loadBrightnessMapFromConfigXml();
loadBrightnessRampsFromConfigXml();
- loadAmbientLightSensorFromConfigXml();
+ mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(mContext.getResources());
+ mProximitySensor = SensorData.loadSensorUnspecifiedConfig();
loadBrightnessChangeThresholdsFromXml();
- setProxSensorUnspecified();
loadAutoBrightnessConfigsFromConfigXml();
loadAutoBrightnessAvailableFromConfigXml();
loadRefreshRateSetting(null);
@@ -1966,8 +1981,8 @@
mBrightnessRampDecreaseMaxIdleMillis = 0;
mBrightnessRampIncreaseMaxIdleMillis = 0;
setSimpleMappingStrategyValues();
- loadAmbientLightSensorFromConfigXml();
- setProxSensorUnspecified();
+ mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(mContext.getResources());
+ mProximitySensor = SensorData.loadSensorUnspecifiedConfig();
loadAutoBrightnessAvailableFromConfigXml();
}
@@ -2919,64 +2934,10 @@
mBrightnessRampSlowDecrease = mBrightnessRampSlowIncrease;
}
- private void loadAmbientLightSensorFromConfigXml() {
- mAmbientLightSensor.name = "";
- mAmbientLightSensor.type = mContext.getResources().getString(
- com.android.internal.R.string.config_displayLightSensorType);
- }
-
private void loadAutoBrightnessConfigsFromConfigXml() {
loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/);
}
- private void loadAmbientLightSensorFromDdc(DisplayConfiguration config) {
- final SensorDetails sensorDetails = config.getLightSensor();
- if (sensorDetails != null) {
- loadSensorData(sensorDetails, mAmbientLightSensor);
- } else {
- loadAmbientLightSensorFromConfigXml();
- }
- }
-
- private void setProxSensorUnspecified() {
- mProximitySensor = new SensorData();
- }
-
- private void loadScreenOffBrightnessSensorFromDdc(DisplayConfiguration config) {
- final SensorDetails sensorDetails = config.getScreenOffBrightnessSensor();
- if (sensorDetails != null) {
- loadSensorData(sensorDetails, mScreenOffBrightnessSensor);
- }
- }
-
- private void loadProxSensorFromDdc(DisplayConfiguration config) {
- SensorDetails sensorDetails = config.getProxSensor();
- if (sensorDetails != null) {
- String name = sensorDetails.getName();
- String type = sensorDetails.getType();
- if ("".equals(name) && "".equals(type)) {
- // <proxSensor> with empty values to the config means no sensor should be used
- mProximitySensor = null;
- } else {
- mProximitySensor = new SensorData();
- loadSensorData(sensorDetails, mProximitySensor);
- }
- } else {
- setProxSensorUnspecified();
- }
- }
-
- private void loadSensorData(@NonNull SensorDetails sensorDetails,
- @NonNull SensorData sensorData) {
- sensorData.name = sensorDetails.getName();
- sensorData.type = sensorDetails.getType();
- final RefreshRateRange rr = sensorDetails.getRefreshRate();
- if (rr != null) {
- sensorData.minRefreshRate = rr.getMinimum().floatValue();
- sensorData.maxRefreshRate = rr.getMaximum().floatValue();
- }
- }
-
private void loadBrightnessChangeThresholdsFromXml() {
loadBrightnessChangeThresholds(/* config= */ null);
}
@@ -3390,37 +3351,6 @@
}
/**
- * Uniquely identifies a Sensor, with the combination of Type and Name.
- */
- public static class SensorData {
- public String type;
- public String name;
- public float minRefreshRate = 0.0f;
- public float maxRefreshRate = Float.POSITIVE_INFINITY;
-
- @Override
- public String toString() {
- return "Sensor{"
- + "type: " + type
- + ", name: " + name
- + ", refreshRateRange: [" + minRefreshRate + ", " + maxRefreshRate + "]"
- + "} ";
- }
-
- /**
- * @return True if the sensor matches both the specified name and type, or one if only one
- * is specified (not-empty). Always returns false if both parameters are null or empty.
- */
- public boolean matches(String sensorName, String sensorType) {
- final boolean isNameSpecified = !TextUtils.isEmpty(sensorName);
- final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType);
- return (isNameSpecified || isTypeSpecified)
- && (!isNameSpecified || sensorName.equals(name))
- && (!isTypeSpecified || sensorType.equals(type));
- }
- }
-
- /**
* Container for high brightness mode configuration data.
*/
static class HighBrightnessModeData {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index eae153c..8046dbf 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -158,7 +158,7 @@
import com.android.server.SystemService;
import com.android.server.UiThread;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
-import com.android.server.display.DisplayDeviceConfig.SensorData;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.Layout;
diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java
new file mode 100644
index 0000000..3bb35bf
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/SensorData.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Uniquely identifies a Sensor, with the combination of Type and Name.
+ */
+public class SensorData {
+
+ @Nullable
+ public final String type;
+ @Nullable
+ public final String name;
+ public final float minRefreshRate;
+ public final float maxRefreshRate;
+ public final List<SupportedMode> supportedModes;
+
+ @VisibleForTesting
+ public SensorData() {
+ this(/* type= */ null, /* name= */ null);
+ }
+
+ @VisibleForTesting
+ public SensorData(String type, String name) {
+ this(type, name, /* minRefreshRate= */ 0f, /* maxRefreshRate= */ Float.POSITIVE_INFINITY);
+ }
+
+ @VisibleForTesting
+ public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate) {
+ this(type, name, minRefreshRate, maxRefreshRate, /* supportedModes= */ List.of());
+ }
+
+ @VisibleForTesting
+ public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate,
+ List<SupportedMode> supportedModes) {
+ this.type = type;
+ this.name = name;
+ this.minRefreshRate = minRefreshRate;
+ this.maxRefreshRate = maxRefreshRate;
+ this.supportedModes = Collections.unmodifiableList(supportedModes);
+ }
+
+ /**
+ * @return True if the sensor matches both the specified name and type, or one if only one
+ * is specified (not-empty). Always returns false if both parameters are null or empty.
+ */
+ public boolean matches(String sensorName, String sensorType) {
+ final boolean isNameSpecified = !TextUtils.isEmpty(sensorName);
+ final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType);
+ return (isNameSpecified || isTypeSpecified)
+ && (!isNameSpecified || sensorName.equals(name))
+ && (!isTypeSpecified || sensorType.equals(type));
+ }
+
+ @Override
+ public String toString() {
+ return "SensorData{"
+ + "type= " + type
+ + ", name= " + name
+ + ", refreshRateRange: [" + minRefreshRate + ", " + maxRefreshRate + "]"
+ + ", supportedModes=" + supportedModes
+ + '}';
+ }
+
+ /**
+ * Loads ambient light sensor data from DisplayConfiguration and if missing from resources xml
+ */
+ public static SensorData loadAmbientLightSensorConfig(DisplayConfiguration config,
+ Resources resources) {
+ SensorDetails sensorDetails = config.getLightSensor();
+ if (sensorDetails != null) {
+ return loadSensorData(sensorDetails);
+ } else {
+ return loadAmbientLightSensorConfig(resources);
+ }
+ }
+
+ /**
+ * Loads ambient light sensor data from resources xml
+ */
+ public static SensorData loadAmbientLightSensorConfig(Resources resources) {
+ return new SensorData(
+ resources.getString(com.android.internal.R.string.config_displayLightSensorType),
+ /* name= */ "");
+ }
+
+ /**
+ * Loads screen off brightness sensor data from DisplayConfiguration
+ */
+ public static SensorData loadScreenOffBrightnessSensorConfig(DisplayConfiguration config) {
+ SensorDetails sensorDetails = config.getScreenOffBrightnessSensor();
+ if (sensorDetails != null) {
+ return loadSensorData(sensorDetails);
+ } else {
+ return new SensorData();
+ }
+ }
+
+ /**
+ * Loads proximity sensor data from DisplayConfiguration
+ */
+ @Nullable
+ public static SensorData loadProxSensorConfig(DisplayConfiguration config) {
+ SensorDetails sensorDetails = config.getProxSensor();
+ if (sensorDetails != null) {
+ String name = sensorDetails.getName();
+ String type = sensorDetails.getType();
+ if ("".equals(name) && "".equals(type)) {
+ // <proxSensor> with empty values to the config means no sensor should be used.
+ // See also {@link com.android.server.display.utils.SensorUtils}
+ return null;
+ } else {
+ return loadSensorData(sensorDetails);
+ }
+ } else {
+ return new SensorData();
+ }
+ }
+
+ /**
+ * Loads sensor unspecified config, this means system should use default sensor.
+ * See also {@link com.android.server.display.utils.SensorUtils}
+ */
+ @NonNull
+ public static SensorData loadSensorUnspecifiedConfig() {
+ return new SensorData();
+ }
+
+ private static SensorData loadSensorData(@NonNull SensorDetails sensorDetails) {
+ float minRefreshRate = 0f;
+ float maxRefreshRate = Float.POSITIVE_INFINITY;
+ RefreshRateRange rr = sensorDetails.getRefreshRate();
+ if (rr != null) {
+ minRefreshRate = rr.getMinimum().floatValue();
+ maxRefreshRate = rr.getMaximum().floatValue();
+ }
+ ArrayList<SupportedMode> supportedModes = new ArrayList<>();
+ NonNegativeFloatToFloatMap configSupportedModes = sensorDetails.getSupportedModes();
+ if (configSupportedModes != null) {
+ for (NonNegativeFloatToFloatPoint supportedMode : configSupportedModes.getPoint()) {
+ supportedModes.add(new SupportedMode(supportedMode.getFirst().floatValue(),
+ supportedMode.getSecond().floatValue()));
+ }
+ }
+
+ return new SensorData(sensorDetails.getType(), sensorDetails.getName(), minRefreshRate,
+ maxRefreshRate, supportedModes);
+ }
+
+ public static class SupportedMode {
+ public final float refreshRate;
+ public final float vsyncRate;
+
+ public SupportedMode(float refreshRate, float vsyncRate) {
+ this.refreshRate = refreshRate;
+ this.vsyncRate = vsyncRate;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java
index 56321cd..8b9fe108 100644
--- a/services/core/java/com/android/server/display/utils/SensorUtils.java
+++ b/services/core/java/com/android/server/display/utils/SensorUtils.java
@@ -21,7 +21,7 @@
import android.hardware.SensorManager;
import android.text.TextUtils;
-import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.config.SensorData;
import java.util.List;
@@ -36,7 +36,7 @@
*/
@Nullable
public static Sensor findSensor(@Nullable SensorManager sensorManager,
- @Nullable DisplayDeviceConfig.SensorData sensorData, int fallbackType) {
+ @Nullable SensorData sensorData, int fallbackType) {
if (sensorData == null) {
return null;
} else {
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 392b86e..681d1a0 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -327,10 +327,10 @@
/**
* Notify our observers of a change in the data activity state of the interface
*/
- private void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos,
+ private void notifyInterfaceClassActivity(int label, boolean isActive, long tsNanos,
int uid) {
invokeForAllObservers(o -> o.interfaceClassDataActivityChanged(
- type, isActive, tsNanos, uid));
+ label, isActive, tsNanos, uid));
}
// Sync the state of the given chain with the native daemon.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 8c75367..7e51526 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -706,6 +706,7 @@
private boolean mNotificationEffectsEnabledForAutomotive;
private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener;
protected NotificationAttentionHelper mAttentionHelper;
+ private boolean mFlagRefactorAttentionHelper;
private int mWarnRemoteViewsSizeBytes;
private int mStripRemoteViewsSizeBytes;
@@ -1189,7 +1190,7 @@
@Override
public void onSetDisabled(int status) {
synchronized (mNotificationLock) {
- if (Flags.refactorAttentionHelper()) {
+ if (mFlagRefactorAttentionHelper) {
mAttentionHelper.updateDisableNotificationEffectsLocked(status);
} else {
mDisableNotificationEffects =
@@ -1335,7 +1336,7 @@
public void clearEffects() {
synchronized (mNotificationLock) {
if (DBG) Slog.d(TAG, "clearEffects");
- if (Flags.refactorAttentionHelper()) {
+ if (mFlagRefactorAttentionHelper) {
mAttentionHelper.clearAttentionEffects();
} else {
clearSoundLocked();
@@ -1564,7 +1565,7 @@
int changedFlags = data.getFlags() ^ flags;
if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
// Suppress notification flag changed, clear any effects
- if (Flags.refactorAttentionHelper()) {
+ if (mFlagRefactorAttentionHelper) {
mAttentionHelper.clearEffectsLocked(key);
} else {
clearEffectsLocked(key);
@@ -1913,7 +1914,7 @@
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (!Flags.refactorAttentionHelper()) {
+ if (!mFlagRefactorAttentionHelper) {
if (action.equals(Intent.ACTION_SCREEN_ON)) {
// Keep track of screen on/off state, but do not turn off the notification light
// until user passes through the lock screen or views the notification.
@@ -2029,7 +2030,7 @@
ContentResolver resolver = getContext().getContentResolver();
resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
false, this, UserHandle.USER_ALL);
- if (!Flags.refactorAttentionHelper()) {
+ if (!mFlagRefactorAttentionHelper) {
resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
false, this, UserHandle.USER_ALL);
}
@@ -2059,7 +2060,7 @@
public void update(Uri uri) {
ContentResolver resolver = getContext().getContentResolver();
- if (!Flags.refactorAttentionHelper()) {
+ if (!mFlagRefactorAttentionHelper) {
if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
boolean pulseEnabled = Settings.System.getIntForUser(resolver,
Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT)
@@ -2560,7 +2561,9 @@
mToastRateLimiter = toastRateLimiter;
- if (Flags.refactorAttentionHelper()) {
+ //Cache aconfig flag value
+ mFlagRefactorAttentionHelper = Flags.refactorAttentionHelper();
+ if (mFlagRefactorAttentionHelper) {
mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
mAccessibilityManager, mPackageManagerClient, userManager, usageStats,
mNotificationManagerPrivate, mZenModeHelper, flagResolver);
@@ -2570,7 +2573,7 @@
// If this is called within a test, make sure to unregister the intent receivers by
// calling onDestroy()
IntentFilter filter = new IntentFilter();
- if (!Flags.refactorAttentionHelper()) {
+ if (!mFlagRefactorAttentionHelper) {
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
@@ -2898,7 +2901,7 @@
}
registerNotificationPreferencesPullers();
new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
- if (Flags.refactorAttentionHelper()) {
+ if (mFlagRefactorAttentionHelper) {
mAttentionHelper.onSystemReady();
}
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
@@ -6571,7 +6574,7 @@
pw.println(" mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
pw.println(" hideSilentStatusBar="
+ mPreferencesHelper.shouldHideSilentStatusIcons());
- if (Flags.refactorAttentionHelper()) {
+ if (mFlagRefactorAttentionHelper) {
mAttentionHelper.dump(pw, " ", filter);
}
}
@@ -7042,9 +7045,8 @@
channelId = (new Notification.TvExtender(notification)).getChannelId();
}
String shortcutId = n.getShortcutId();
- final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
- pkg, notificationUid, channelId, shortcutId,
- true /* parent ok */, false /* includeDeleted */);
+ final NotificationChannel channel = getNotificationChannelRestoreDeleted(pkg,
+ callingUid, notificationUid, channelId, shortcutId);
if (channel == null) {
final String noChannelStr = "No Channel found for "
+ "pkg=" + pkg
@@ -7162,6 +7164,35 @@
return true;
}
+ /**
+ * Returns a channel, if exists, and restores deleted conversation channels.
+ */
+ @Nullable
+ private NotificationChannel getNotificationChannelRestoreDeleted(String pkg,
+ int callingUid, int notificationUid, String channelId, String conversationId) {
+ // Restore a deleted conversation channel, if exists. Otherwise use the parent channel.
+ NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
+ pkg, notificationUid, channelId, conversationId,
+ true /* parent ok */, !TextUtils.isEmpty(conversationId) /* includeDeleted */);
+ // Restore deleted conversation channel
+ if (channel != null && channel.isDeleted()) {
+ if (Objects.equals(conversationId, channel.getConversationId())) {
+ boolean needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(
+ pkg, notificationUid, channel, true /* fromTargetApp */,
+ mConditionProviders.isPackageOrComponentAllowed(pkg,
+ UserHandle.getUserId(notificationUid)), callingUid, true);
+ // Update policy file if the conversation channel was restored
+ if (needsPolicyFileChange) {
+ handleSavePolicyFile();
+ }
+ } else {
+ // Do not restore parent channel
+ channel = null;
+ }
+ }
+ return channel;
+ }
+
private void onConversationRemovedInternal(String pkg, int uid, Set<String> shortcuts) {
checkCallerIsSystem();
Preconditions.checkStringNotEmpty(pkg);
@@ -7844,7 +7875,7 @@
boolean wasPosted = removeFromNotificationListsLocked(r);
cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null,
SystemClock.elapsedRealtime());
- if (Flags.refactorAttentionHelper()) {
+ if (mFlagRefactorAttentionHelper) {
mAttentionHelper.updateLightsLocked();
} else {
updateLightsLocked();
@@ -7984,7 +8015,7 @@
cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
mSendDelete, childrenFlagChecker, mReason,
mCancellationElapsedTimeMs);
- if (Flags.refactorAttentionHelper()) {
+ if (mFlagRefactorAttentionHelper) {
mAttentionHelper.updateLightsLocked();
} else {
updateLightsLocked();
@@ -8281,7 +8312,7 @@
int buzzBeepBlinkLoggingCode = 0;
if (!r.isHidden()) {
- if (Flags.refactorAttentionHelper()) {
+ if (mFlagRefactorAttentionHelper) {
buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r,
new NotificationAttentionHelper.Signals(
mUserProfiles.isCurrentProfile(r.getUserId()),
@@ -9268,7 +9299,7 @@
|| interruptiveChanged;
if (interceptBefore && !record.isIntercepted()
&& record.isNewEnoughForAlerting(System.currentTimeMillis())) {
- if (Flags.refactorAttentionHelper()) {
+ if (mFlagRefactorAttentionHelper) {
mAttentionHelper.buzzBeepBlinkLocked(record,
new NotificationAttentionHelper.Signals(
mUserProfiles.isCurrentProfile(record.getUserId()), mListenerHints));
@@ -9648,7 +9679,7 @@
});
}
- if (Flags.refactorAttentionHelper()) {
+ if (mFlagRefactorAttentionHelper) {
mAttentionHelper.clearEffectsLocked(canceledKey);
} else {
// sound
@@ -10012,7 +10043,7 @@
cancellationElapsedTimeMs);
}
}
- if (Flags.refactorAttentionHelper()) {
+ if (mFlagRefactorAttentionHelper) {
mAttentionHelper.updateLightsLocked();
} else {
updateLightsLocked();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 81a570f..4e14c90 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1388,10 +1388,32 @@
final long identity = Binder.clearCallingIdentity();
try {
+ // QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL is only allowed for managed-profiles
+ if (dontAskCredential) {
+ UserInfo userInfo;
+ synchronized (mUsersLock) {
+ userInfo = getUserInfo(userId);
+ }
+ if (!userInfo.isManagedProfile()) {
+ throw new IllegalArgumentException("Invalid flags: " + flags
+ + ". Can't skip credential check for the user");
+ }
+ }
if (enableQuietMode) {
setQuietModeEnabled(userId, true /* enableQuietMode */, target, callingPackage);
return true;
}
+ if (android.os.Flags.allowPrivateProfile()) {
+ final UserProperties userProperties = getUserPropertiesInternal(userId);
+ if (userProperties != null
+ && userProperties.isAuthAlwaysRequiredToDisableQuietMode()) {
+ if (onlyIfCredentialNotRequired) {
+ return false;
+ }
+ showConfirmCredentialToDisableQuietMode(userId, target);
+ return false;
+ }
+ }
final boolean hasUnifiedChallenge =
mLockPatternUtils.isManagedProfileWithUnifiedChallenge(userId);
if (hasUnifiedChallenge) {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 29e0c35..7da76c1 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -193,6 +193,7 @@
.setStartWithParent(true)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
.setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+ .setAuthAlwaysRequiredToDisableQuietMode(false)
.setCredentialShareableWithParent(true));
}
@@ -292,7 +293,8 @@
.setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
- .setCredentialShareableWithParent(false)
+ .setCredentialShareableWithParent(true)
+ .setAuthAlwaysRequiredToDisableQuietMode(true)
.setMediaSharedWithParent(false)
.setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
.setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 215934f..cca4261 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -455,6 +455,20 @@
<xs:annotation name="nullable"/>
<xs:annotation name="final"/>
</xs:element>
+ <!-- list of supported modes when sensor is ON. Each point corresponds to one mode.
+ Mode format is : first = refreshRate, second = vsyncRate. E.g. :
+ <supportedModes>
+ <point>
+ <first>60</first> // refreshRate
+ <second>60</second> //vsyncRate
+ </point>
+ ....
+ </supportedModes>
+ -->
+ <xs:element type="nonNegativeFloatToFloatMap" name="supportedModes" minOccurs="0">
+ <xs:annotation name="nullable"/>
+ <xs:annotation name="final"/>
+ </xs:element>
</xs:sequence>
</xs:complexType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index f7e0043..f767291 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -349,9 +349,11 @@
ctor public SensorDetails();
method @Nullable public final String getName();
method @Nullable public final com.android.server.display.config.RefreshRateRange getRefreshRate();
+ method @Nullable public final com.android.server.display.config.NonNegativeFloatToFloatMap getSupportedModes();
method @Nullable public final String getType();
method public final void setName(@Nullable String);
method public final void setRefreshRate(@Nullable com.android.server.display.config.RefreshRateRange);
+ method public final void setSupportedModes(@Nullable com.android.server.display.config.NonNegativeFloatToFloatMap);
method public final void setType(@Nullable String);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 179a9d5..0bcbeb9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -17,9 +17,12 @@
package com.android.server.display;
+import static com.android.server.display.config.SensorData.SupportedMode;
import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -526,6 +529,26 @@
}
@Test
+ public void testProximitySensorWithRefreshRatesFromDisplayConfig() throws IOException {
+ setupDisplayDeviceConfigFromDisplayConfigFile(
+ getContent(getValidLuxThrottling(), getValidProxSensorWithRefreshRateAndVsyncRate(),
+ /* includeIdleMode= */ true));
+ assertEquals("test_proximity_sensor",
+ mDisplayDeviceConfig.getProximitySensor().type);
+ assertEquals("Test Proximity Sensor",
+ mDisplayDeviceConfig.getProximitySensor().name);
+ assertEquals(mDisplayDeviceConfig.getProximitySensor().minRefreshRate, 60, SMALL_DELTA);
+ assertEquals(mDisplayDeviceConfig.getProximitySensor().maxRefreshRate, 90, SMALL_DELTA);
+ assertThat(mDisplayDeviceConfig.getProximitySensor().supportedModes).hasSize(2);
+ SupportedMode mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(0);
+ assertEquals(mode.refreshRate, 60, SMALL_DELTA);
+ assertEquals(mode.vsyncRate, 65, SMALL_DELTA);
+ mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(1);
+ assertEquals(mode.refreshRate, 120, SMALL_DELTA);
+ assertEquals(mode.vsyncRate, 125, SMALL_DELTA);
+ }
+
+ @Test
public void testBlockingZoneThresholdsFromDisplayConfig() throws IOException {
setupDisplayDeviceConfigFromDisplayConfigFile();
@@ -821,6 +844,27 @@
+ "</proxSensor>\n";
}
+ private String getValidProxSensorWithRefreshRateAndVsyncRate() {
+ return "<proxSensor>\n"
+ + "<type>test_proximity_sensor</type>\n"
+ + "<name>Test Proximity Sensor</name>\n"
+ + "<refreshRate>\n"
+ + "<minimum>60</minimum>\n"
+ + "<maximum>90</maximum>\n"
+ + "</refreshRate>\n"
+ + "<supportedModes>\n"
+ + "<point>\n"
+ + "<first>60</first>\n" // refreshRate
+ + "<second>65</second>\n" //vsyncRate
+ + "</point>\n"
+ + "<point>\n"
+ + "<first>120</first>\n" // refreshRate
+ + "<second>125</second>\n" //vsyncRate
+ + "</point>\n"
+ + "</supportedModes>"
+ + "</proxSensor>\n";
+ }
+
private String getProxSensorWithEmptyValues() {
return "<proxSensor>\n"
+ "<type></type>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 9684f42..3775ac94 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -123,6 +123,7 @@
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.DisplayManagerService.DeviceStateListener;
import com.android.server.display.DisplayManagerService.SyncRoot;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.notifications.DisplayNotificationManager;
import com.android.server.input.InputManagerInternal;
@@ -2317,11 +2318,8 @@
String testSensorType = "testType";
Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName);
- DisplayDeviceConfig.SensorData sensorData = new DisplayDeviceConfig.SensorData();
- sensorData.type = testSensorType;
- sensorData.name = testSensorName;
- sensorData.minRefreshRate = 10f;
- sensorData.maxRefreshRate = 100f;
+ SensorData sensorData = new SensorData(testSensorType, testSensorName,
+ /* minRefreshRate= */ 10f, /* maxRefreshRate= */ 100f);
when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(sensorData);
when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(Collections.singletonList(
@@ -2352,12 +2350,6 @@
String testSensorType = "testType";
Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName);
- DisplayDeviceConfig.SensorData sensorData = new DisplayDeviceConfig.SensorData();
- sensorData.type = testSensorType;
- sensorData.name = testSensorName;
- sensorData.minRefreshRate = 10f;
- sensorData.maxRefreshRate = 100f;
-
when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(null);
when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(Collections.singletonList(
testSensor));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 47521d1..57f392a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -77,6 +77,7 @@
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.feature.flags.Flags;
import com.android.server.display.layout.Layout;
@@ -1618,23 +1619,13 @@
when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
- new DisplayDeviceConfig.SensorData() {
- {
- type = Sensor.STRING_TYPE_PROXIMITY;
- name = null;
- }
- });
+ new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
- new DisplayDeviceConfig.SensorData());
+ new SensorData());
when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
- new DisplayDeviceConfig.SensorData() {
- {
- type = Sensor.STRING_TYPE_LIGHT;
- name = null;
- }
- });
+ new SensorData(Sensor.STRING_TYPE_LIGHT, null));
when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
.thenReturn(new int[0]);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 37ee23f..9617bd0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -76,6 +76,7 @@
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.config.SensorData;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.feature.flags.Flags;
import com.android.server.display.layout.Layout;
@@ -1515,23 +1516,13 @@
when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
- new DisplayDeviceConfig.SensorData() {
- {
- type = Sensor.STRING_TYPE_PROXIMITY;
- name = null;
- }
- });
+ new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
- new DisplayDeviceConfig.SensorData());
+ new SensorData());
when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
- new DisplayDeviceConfig.SensorData() {
- {
- type = Sensor.STRING_TYPE_LIGHT;
- name = null;
- }
- });
+ new SensorData(Sensor.STRING_TYPE_LIGHT, null));
when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
.thenReturn(new int[0]);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
index 534a708..ebd6614 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -37,6 +37,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.server.display.config.SensorData;
import com.android.server.testutils.OffsettableClock;
import org.junit.Before;
@@ -74,14 +75,7 @@
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
- new DisplayDeviceConfig.SensorData() {
- {
- type = Sensor.STRING_TYPE_PROXIMITY;
- // This is kept null because currently there is no way to define a sensor
- // name in TestUtils
- name = null;
- }
- });
+ new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
setUpProxSensor();
DisplayPowerProximityStateController.Injector injector =
new DisplayPowerProximityStateController.Injector() {
@@ -171,13 +165,7 @@
@Test
public void isProximitySensorAvailableReturnsFalseWhenNotAvailableAndNoDefault() {
- when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
- new DisplayDeviceConfig.SensorData() {
- {
- type = null;
- name = null;
- }
- });
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
@@ -188,13 +176,7 @@
@Test
public void isProximitySensorAvailableReturnsTrueWhenNotAvailableAndHasDefault()
throws Exception {
- when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
- new DisplayDeviceConfig.SensorData() {
- {
- type = null;
- name = null;
- }
- });
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
@@ -207,13 +189,7 @@
@Test
public void isProximitySensorAvailableReturnsFalseWhenNotAvailableHasDefaultNonDefaultDisplay()
throws Exception {
- when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
- new DisplayDeviceConfig.SensorData() {
- {
- type = null;
- name = null;
- }
- });
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
@@ -240,12 +216,7 @@
public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
- new DisplayDeviceConfig.SensorData() {
- {
- type = Sensor.STRING_TYPE_PROXIMITY;
- name = null;
- }
- });
+ new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
Sensor newProxSensor = TestUtils.createSensor(
Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
index 4494b0c..6e2d954 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
@@ -28,7 +28,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.annotations.Keep;
-import com.android.server.display.DisplayDeviceConfig.SensorData;
+import com.android.server.display.config.SensorData;
import org.junit.Before;
import org.junit.Test;
@@ -123,9 +123,7 @@
when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(allSensors);
when(mSensorManager.getDefaultSensor(fallbackType)).thenReturn(defaultSensor);
- SensorData sensorData = new SensorData();
- sensorData.name = sensorName;
- sensorData.type = sensorType;
+ SensorData sensorData = new SensorData(sensorType, sensorName);
Sensor result = SensorUtils.findSensor(mSensorManager, sensorData, fallbackType);
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index ef19ba1..e89199d 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -39,6 +39,7 @@
crossProfileIntentResolutionStrategy='0'
mediaSharedWithParent='true'
credentialShareableWithParent='false'
+ authAlwaysRequiredToDisableQuietMode='true'
showInSettings='23'
hideInSettingsInQuietMode='true'
inheritDevicePolicy='450'
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index c684a7b..57b1225 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -67,6 +67,7 @@
.setCrossProfileIntentResolutionStrategy(0)
.setMediaSharedWithParent(false)
.setCredentialShareableWithParent(true)
+ .setAuthAlwaysRequiredToDisableQuietMode(false)
.setDeleteAppWithParent(false)
.setAlwaysVisible(false)
.build();
@@ -80,6 +81,7 @@
actualProps.setCrossProfileIntentResolutionStrategy(1);
actualProps.setMediaSharedWithParent(true);
actualProps.setCredentialShareableWithParent(false);
+ actualProps.setAuthAlwaysRequiredToDisableQuietMode(true);
actualProps.setDeleteAppWithParent(true);
actualProps.setAlwaysVisible(true);
@@ -123,6 +125,7 @@
.setInheritDevicePolicy(1732)
.setMediaSharedWithParent(true)
.setDeleteAppWithParent(true)
+ .setAuthAlwaysRequiredToDisableQuietMode(false)
.setAlwaysVisible(true)
.build();
final UserProperties orig = new UserProperties(defaultProps);
@@ -131,6 +134,7 @@
orig.setShowInSettings(1437);
orig.setInheritDevicePolicy(9456);
orig.setDeleteAppWithParent(false);
+ orig.setAuthAlwaysRequiredToDisableQuietMode(true);
orig.setAlwaysVisible(false);
// Test every permission level. (Currently, it's linear so it's easy.)
@@ -182,6 +186,8 @@
hasManagePermission);
assertEqualGetterOrThrows(orig::getUseParentsContacts,
copy::getUseParentsContacts, hasManagePermission);
+ assertEqualGetterOrThrows(orig::isAuthAlwaysRequiredToDisableQuietMode,
+ copy::isAuthAlwaysRequiredToDisableQuietMode, hasManagePermission);
// Items requiring hasQueryPermission - put them here using hasQueryPermission.
@@ -242,6 +248,8 @@
.isEqualTo(actual.isMediaSharedWithParent());
assertThat(expected.isCredentialShareableWithParent())
.isEqualTo(actual.isCredentialShareableWithParent());
+ assertThat(expected.isAuthAlwaysRequiredToDisableQuietMode())
+ .isEqualTo(actual.isAuthAlwaysRequiredToDisableQuietMode());
assertThat(expected.getDeleteAppWithParent()).isEqualTo(actual.getDeleteAppWithParent());
assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible());
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 20270a8..48eb5c6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -89,6 +89,7 @@
.setCrossProfileIntentResolutionStrategy(1)
.setMediaSharedWithParent(true)
.setCredentialShareableWithParent(false)
+ .setAuthAlwaysRequiredToDisableQuietMode(true)
.setShowInSettings(900)
.setHideInSettingsInQuietMode(true)
.setInheritDevicePolicy(340)
@@ -160,6 +161,8 @@
.getCrossProfileIntentResolutionStrategy());
assertTrue(type.getDefaultUserPropertiesReference().isMediaSharedWithParent());
assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent());
+ assertTrue(type.getDefaultUserPropertiesReference()
+ .isAuthAlwaysRequiredToDisableQuietMode());
assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings());
assertTrue(type.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
assertEquals(340, type.getDefaultUserPropertiesReference()
@@ -306,6 +309,7 @@
.setCrossProfileIntentResolutionStrategy(1)
.setMediaSharedWithParent(false)
.setCredentialShareableWithParent(true)
+ .setAuthAlwaysRequiredToDisableQuietMode(false)
.setShowInSettings(20)
.setHideInSettingsInQuietMode(false)
.setInheritDevicePolicy(21)
@@ -347,6 +351,8 @@
assertFalse(aospType.getDefaultUserPropertiesReference().isMediaSharedWithParent());
assertTrue(aospType.getDefaultUserPropertiesReference()
.isCredentialShareableWithParent());
+ assertFalse(aospType.getDefaultUserPropertiesReference()
+ .isAuthAlwaysRequiredToDisableQuietMode());
assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings());
assertFalse(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
assertEquals(21, aospType.getDefaultUserPropertiesReference()
@@ -394,6 +400,8 @@
assertTrue(aospType.getDefaultUserPropertiesReference().isMediaSharedWithParent());
assertFalse(aospType.getDefaultUserPropertiesReference()
.isCredentialShareableWithParent());
+ assertTrue(aospType.getDefaultUserPropertiesReference()
+ .isAuthAlwaysRequiredToDisableQuietMode());
assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings());
assertTrue(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
assertEquals(450, aospType.getDefaultUserPropertiesReference()
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 775d42a..2b6d8ed 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -331,6 +331,9 @@
.isEqualTo(privateProfileUserProperties.isMediaSharedWithParent());
assertThat(typeProps.isCredentialShareableWithParent())
.isEqualTo(privateProfileUserProperties.isCredentialShareableWithParent());
+ assertThat(typeProps.isAuthAlwaysRequiredToDisableQuietMode())
+ .isEqualTo(privateProfileUserProperties
+ .isAuthAlwaysRequiredToDisableQuietMode());
assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
// Verify private profile parent
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3803244..7fb8b30 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3239,7 +3239,7 @@
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
- new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
+ new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
@@ -9927,6 +9927,174 @@
}
@Test
+ public void testRestoreConversationChannel_deleted() throws Exception {
+ // Create parent channel
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+ IMPORTANCE_DEFAULT);
+ NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+ NotificationChannel.CREATOR);
+ assertEquals(originalChannel, parentChannel);
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(parentChannel)));
+
+ //Create deleted conversation channel
+ mBinderService.createConversationNotificationChannelForPackage(
+ PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID);
+ final NotificationChannel conversationChannel =
+ mBinderService.getConversationNotificationChannel(
+ PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID);
+ conversationChannel.setDeleted(true);
+
+ //Create notification record
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+ null /* groupKey */, false /* isSummary */);
+ nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
+ nb.setChannelId(originalChannel.getId());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+ assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Verify that the channel was changed to the conversation channel and restored
+ assertThat(mService.getNotificationRecord(nr.getKey()).isConversation()).isTrue();
+ assertThat(mService.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo(
+ conversationChannel);
+ assertThat(mService.getNotificationRecord(nr.getKey()).getChannel().isDeleted()).isFalse();
+ assertThat(mService.getNotificationRecord(nr.getKey()).getChannel().getDeletedTimeMs())
+ .isEqualTo(-1);
+ }
+
+ @Test
+ public void testDoNotRestoreParentChannel_deleted() throws Exception {
+ // Create parent channel and set as deleted
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+ IMPORTANCE_DEFAULT);
+ NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+ NotificationChannel.CREATOR);
+ assertEquals(originalChannel, parentChannel);
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(parentChannel)));
+ parentChannel.setDeleted(true);
+
+ //Create notification record
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+ null /* groupKey */, false /* isSummary */);
+ nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
+ nb.setChannelId(originalChannel.getId());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+ assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Verify that the channel was not restored and the notification was not posted
+ assertThat(mService.mChannelToastsSent).contains(mUid);
+ assertThat(mService.getNotificationRecord(nr.getKey())).isNull();
+ assertThat(parentChannel.isDeleted()).isTrue();
+ }
+
+ @Test
+ public void testEnqueueToConversationChannel_notDeleted_doesNotRestore() throws Exception {
+ TestableNotificationManagerService service = spy(mService);
+ PreferencesHelper preferencesHelper = spy(mService.mPreferencesHelper);
+ service.setPreferencesHelper(preferencesHelper);
+ // Create parent channel
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+ IMPORTANCE_DEFAULT);
+ NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+ NotificationChannel.CREATOR);
+ assertEquals(originalChannel, parentChannel);
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(parentChannel)));
+
+ //Create conversation channel
+ mBinderService.createConversationNotificationChannelForPackage(
+ PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID);
+ final NotificationChannel conversationChannel =
+ mBinderService.getConversationNotificationChannel(
+ PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID);
+
+ //Create notification record
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+ null /* groupKey */, false /* isSummary */);
+ nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
+ nb.setChannelId(originalChannel.getId());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+ assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Verify that the channel was changed to the conversation channel and not restored
+ assertThat(service.getNotificationRecord(nr.getKey()).isConversation()).isTrue();
+ assertThat(service.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo(
+ conversationChannel);
+ verify(service, never()).handleSavePolicyFile();
+ verify(preferencesHelper, never()).createNotificationChannel(anyString(),
+ anyInt(), any(), anyBoolean(), anyBoolean(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testEnqueueToParentChannel_notDeleted_doesNotRestore() throws Exception {
+ TestableNotificationManagerService service = spy(mService);
+ PreferencesHelper preferencesHelper = spy(mService.mPreferencesHelper);
+ service.setPreferencesHelper(preferencesHelper);
+ // Create parent channel
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+ IMPORTANCE_DEFAULT);
+ NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+ NotificationChannel.CREATOR);
+ assertEquals(originalChannel, parentChannel);
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(parentChannel)));
+
+ //Create deleted conversation channel
+ mBinderService.createConversationNotificationChannelForPackage(
+ PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID);
+ final NotificationChannel conversationChannel =
+ mBinderService.getConversationNotificationChannel(
+ PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID);
+
+ //Create notification record without a shortcutId
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+ null /* groupKey */, false /* isSummary */);
+ nb.setShortcutId(null);
+ nb.setChannelId(originalChannel.getId());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+ assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Verify that the channel is the parent channel and no channel was restored
+ //assertThat(service.getNotificationRecord(nr.getKey()).isConversation()).isFalse();
+ assertThat(service.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo(
+ parentChannel);
+ verify(service, never()).handleSavePolicyFile();
+ verify(preferencesHelper, never()).createNotificationChannel(anyString(),
+ anyInt(), any(), anyBoolean(), anyBoolean(), anyInt(), anyBoolean());
+ }
+
+ @Test
public void testGetConversationsForPackage_hasShortcut() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
ArrayList<ConversationChannelWrapper> convos = new ArrayList<>();
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index fd4ec8b..5949bca 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -284,6 +284,9 @@
"hoststubgen-helper-runtime.ravenwood",
"framework-minus-apex.ravenwood",
],
+ static_libs: [
+ "core-xml-for-device",
+ ],
}
// Defaults for host side test modules.