Merge "Make A11y feature flags accessible to CTS tests" into main
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 6871762..762e2af 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -366,7 +366,7 @@
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
- stopUser(userId, /* force */true);
+ stopUser(userId);
mRunner.resumeTimingForNextIteration();
}
@@ -429,7 +429,7 @@
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
- stopUser(userId, /* force */true);
+ stopUser(userId);
mRunner.resumeTimingForNextIteration();
}
@@ -545,7 +545,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
switchUserNoCheck(currentUserId);
- stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
attestFalse("Failed to stop user " + userId, mAm.isUserRunning(userId));
mRunner.resumeTimingForNextIteration();
}
@@ -571,7 +571,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
switchUserNoCheck(startUser);
- stopUserAfterWaitingForBroadcastIdle(testUser, true);
+ stopUserAfterWaitingForBroadcastIdle(testUser);
attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
mRunner.resumeTimingForNextIteration();
}
@@ -660,7 +660,7 @@
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
- stopUser(userId, false);
+ stopUser(userId);
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
@@ -685,7 +685,7 @@
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
- stopUser(userId, false);
+ stopUser(userId);
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
@@ -883,7 +883,7 @@
final int userId = createManagedProfile();
// Start the profile initially, then stop it. Similar to setQuietModeEnabled.
startUserInBackgroundAndWaitForUnlock(userId);
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -905,7 +905,7 @@
startUserInBackgroundAndWaitForUnlock(userId);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -987,7 +987,7 @@
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -1019,7 +1019,7 @@
installPreexistingApp(userId, DUMMY_PACKAGE_NAME);
startUserInBackgroundAndWaitForUnlock(userId);
startApp(userId, DUMMY_PACKAGE_NAME);
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
SystemClock.sleep(1_000); // 1 second cool-down before re-starting profile.
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -1144,7 +1144,7 @@
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
- stopUser(userId, true);
+ stopUser(userId);
mRunner.pauseTiming();
Log.i(TAG, "Stopping timer");
@@ -1168,7 +1168,7 @@
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
- stopUser(userId, true);
+ stopUser(userId);
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
@@ -1294,15 +1294,15 @@
* Do not call this method while timing is on. i.e. between mRunner.resumeTiming() and
* mRunner.pauseTiming(). Otherwise it would cause the test results to be spiky.
*/
- private void stopUserAfterWaitingForBroadcastIdle(int userId, boolean force)
+ private void stopUserAfterWaitingForBroadcastIdle(int userId)
throws RemoteException {
waitForBroadcastIdle();
- stopUser(userId, force);
+ stopUser(userId);
}
- private void stopUser(int userId, boolean force) throws RemoteException {
+ private void stopUser(int userId) throws RemoteException {
final CountDownLatch latch = new CountDownLatch(1);
- mIam.stopUser(userId, force /* force */, new IStopUserCallback.Stub() {
+ mIam.stopUserWithCallback(userId, new IStopUserCallback.Stub() {
@Override
public void userStopped(int userId) throws RemoteException {
latch.countDown();
@@ -1352,7 +1352,7 @@
attestTrue("Didn't switch back to user, " + origUser, origUser == mAm.getCurrentUser());
if (stopNewUser) {
- stopUserAfterWaitingForBroadcastIdle(testUser, true);
+ stopUserAfterWaitingForBroadcastIdle(testUser);
attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
}
@@ -1471,7 +1471,7 @@
}
private void removeUser(int userId) throws RemoteException {
- stopUserAfterWaitingForBroadcastIdle(userId, true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
try {
ShellHelper.runShellCommandWithTimeout("pm remove-user -w " + userId,
TIMEOUT_IN_SECOND);
@@ -1512,7 +1512,7 @@
final boolean preStartComplete = mIam.startUserInBackgroundWithListener(userId,
preWaiter) && preWaiter.waitForFinish(TIMEOUT_IN_SECOND * 1000);
- stopUserAfterWaitingForBroadcastIdle(userId, /* force */true);
+ stopUserAfterWaitingForBroadcastIdle(userId);
assertTrue("Pre start was not performed for user" + userId, preStartComplete);
}
diff --git a/apex/jobscheduler/OWNERS b/apex/jobscheduler/OWNERS
index 58434f1..22b6489 100644
--- a/apex/jobscheduler/OWNERS
+++ b/apex/jobscheduler/OWNERS
@@ -2,7 +2,6 @@
ctate@google.com
dplotnikov@google.com
jji@google.com
-kwekua@google.com
omakoto@google.com
suprabh@google.com
varunshah@google.com
diff --git a/apex/jobscheduler/framework/java/android/app/job/OWNERS b/apex/jobscheduler/framework/java/android/app/job/OWNERS
index b4a45f5..0b1e559 100644
--- a/apex/jobscheduler/framework/java/android/app/job/OWNERS
+++ b/apex/jobscheduler/framework/java/android/app/job/OWNERS
@@ -4,4 +4,3 @@
omakoto@google.com
ctate@android.com
ctate@google.com
-kwekua@google.com
diff --git a/cmds/incident_helper/OWNERS b/cmds/incident_helper/OWNERS
index cede4ea..29f44ab 100644
--- a/cmds/incident_helper/OWNERS
+++ b/cmds/incident_helper/OWNERS
@@ -1,3 +1,2 @@
joeo@google.com
-kwekua@google.com
yanmin@google.com
diff --git a/core/api/current.txt b/core/api/current.txt
index b19c3ab..13d5e03 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9877,7 +9877,7 @@
ctor public ObservingDevicePresenceRequest.Builder();
method @NonNull public android.companion.ObservingDevicePresenceRequest build();
method @NonNull public android.companion.ObservingDevicePresenceRequest.Builder setAssociationId(int);
- method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid);
+ method @NonNull @RequiresPermission(allOf={android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN}) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid);
}
public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9114f0e..3b988e1 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -157,7 +157,7 @@
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean startUserInBackgroundVisibleOnDisplay(int, int);
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int);
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void unregisterUidFrozenStateChangedCallback(@NonNull android.app.ActivityManager.UidFrozenStateChangedCallback);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -1765,15 +1765,12 @@
public final class InputManager {
method public void addUniqueIdAssociation(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
- method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
- method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+ method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors();
method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
method public int getMousePointerSpeed();
method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
- method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
method public void removeUniqueIdAssociation(@NonNull String);
- method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 7725561..0dab3de 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -5071,7 +5071,7 @@
* <p><b>NOTE:</b> differently from {@link #switchUser(int)}, which stops the current foreground
* user before starting a new one, this method does not stop the previous user running in
* background in the display, and it will return {@code false} in this case. It's up to the
- * caller to call {@link #stopUser(int, boolean)} before starting a new user.
+ * caller to call {@link #stopUser(int)} before starting a new user.
*
* @param userId user to be started in the display. It will return {@code false} if the user is
* a profile, the {@link #getCurrentUser()}, the {@link UserHandle#SYSTEM system user}, or
@@ -5281,15 +5281,16 @@
*
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@TestApi
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
- public boolean stopUser(@UserIdInt int userId, boolean force) {
+ public boolean stopUser(@UserIdInt int userId) {
if (userId == UserHandle.USER_SYSTEM) {
return false;
}
try {
- return USER_OP_SUCCESS == getService().stopUser(
- userId, force, /* callback= */ null);
+ return USER_OP_SUCCESS == getService().stopUserWithCallback(
+ userId, /* callback= */ null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index e094ac6..9ea55f5 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -2205,19 +2205,6 @@
}
/**
- * Sets background activity launch logic won't use pending intent creator foreground state.
- *
- * @hide
- * @deprecated use {@link #setPendingIntentCreatorBackgroundActivityStartMode(int)} instead
- */
- @Deprecated
- public ActivityOptions setIgnorePendingIntentCreatorForegroundState(boolean ignore) {
- mPendingIntentCreatorBackgroundActivityStartMode = ignore
- ? MODE_BACKGROUND_ACTIVITY_START_DENIED : MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
- return this;
- }
-
- /**
* Allow a {@link PendingIntent} to use the privilege of its creator to start background
* activities.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e53bd39..3575545 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1082,6 +1082,8 @@
public boolean managed;
public boolean mallocInfo;
public boolean runGc;
+ // compression format to dump bitmaps, null if no bitmaps to be dumped
+ public String dumpBitmaps;
String path;
ParcelFileDescriptor fd;
RemoteCallback finishCallback;
@@ -1486,11 +1488,12 @@
}
@Override
- public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
- ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+ public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String dumpBitmaps,
+ String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
DumpHeapData dhd = new DumpHeapData();
dhd.managed = managed;
dhd.mallocInfo = mallocInfo;
+ dhd.dumpBitmaps = dumpBitmaps;
dhd.runGc = runGc;
dhd.path = path;
try {
@@ -6859,6 +6862,9 @@
System.runFinalization();
System.gc();
}
+ if (dhd.dumpBitmaps != null) {
+ Bitmap.dumpAll(dhd.dumpBitmaps);
+ }
try (ParcelFileDescriptor fd = dhd.fd) {
if (dhd.managed) {
Debug.dumpHprofData(dhd.path, fd.getFileDescriptor());
@@ -6886,6 +6892,9 @@
if (dhd.finishCallback != null) {
dhd.finishCallback.sendResult(null);
}
+ if (dhd.dumpBitmaps != null) {
+ Bitmap.dumpAll(null); // clear dump
+ }
}
final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ee0225f..e3380e0 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -21,12 +21,14 @@
import static android.os.StrictMode.vmIncorrectContextUseEnabled;
import static android.view.WindowManager.LayoutParams.WindowType;
+import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UiContext;
+import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
@@ -2288,7 +2290,35 @@
Log.v(TAG, "Treating renounced permission " + permission + " as denied");
return PERMISSION_DENIED;
}
- return PermissionManager.checkPermission(permission, pid, uid, getDeviceId());
+
+ // When checking a device-aware permission on a remote device, if the permission is CAMERA
+ // or RECORD_AUDIO we need to check remote device's corresponding capability. If the remote
+ // device doesn't have capability fall back to checking permission on the default device.
+ // Note: we only perform permission check redirection when the device id is not explicitly
+ // set in the context.
+ int deviceId = getDeviceId();
+ if (deviceId != Context.DEVICE_ID_DEFAULT
+ && !mIsExplicitDeviceId
+ && PermissionManager.DEVICE_AWARE_PERMISSIONS.contains(permission)) {
+ VirtualDeviceManager virtualDeviceManager =
+ getSystemService(VirtualDeviceManager.class);
+ VirtualDevice virtualDevice = virtualDeviceManager.getVirtualDevice(deviceId);
+ if (virtualDevice != null) {
+ if ((Objects.equals(permission, Manifest.permission.RECORD_AUDIO)
+ && !virtualDevice.hasCustomAudioInputSupport())
+ || (Objects.equals(permission, Manifest.permission.CAMERA)
+ && !virtualDevice.hasCustomCameraSupport())) {
+ deviceId = Context.DEVICE_ID_DEFAULT;
+ }
+ } else {
+ Slog.e(
+ TAG,
+ "virtualDevice is not found when device id is not default. deviceId = "
+ + deviceId);
+ }
+ }
+
+ return PermissionManager.checkPermission(permission, pid, uid, deviceId);
}
/** @hide */
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 85611e8..dca164d 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -394,7 +394,7 @@
oneway void getMimeTypeFilterAsync(in Uri uri, int userId, in RemoteCallback resultCallback);
// Cause the specified process to dump the specified heap.
boolean dumpHeap(in String process, int userId, boolean managed, boolean mallocInfo,
- boolean runGc, in String path, in ParcelFileDescriptor fd,
+ boolean runGc, in String dumpBitmaps, in String path, in ParcelFileDescriptor fd,
in RemoteCallback finishCallback);
@UnsupportedAppUsage
boolean isUserRunning(int userid, int flags);
@@ -452,12 +452,14 @@
in IBinder resultTo, in String resultWho, int requestCode, int flags,
in ProfilerInfo profilerInfo, in Bundle options, int userId);
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
- int stopUser(int userid, boolean force, in IStopUserCallback callback);
+ int stopUser(int userid, boolean stopProfileRegardlessOfParent, in IStopUserCallback callback);
+ int stopUserWithCallback(int userid, in IStopUserCallback callback);
+ int stopUserExceptCertainProfiles(int userid, boolean stopProfileRegardlessOfParent, in IStopUserCallback callback);
/**
- * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)}
+ * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, IStopUserCallback)}
* for details.
*/
- int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback);
+ int stopUserWithDelayedLocking(int userid, in IStopUserCallback callback);
@UnsupportedAppUsage
void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name);
@@ -499,6 +501,7 @@
in String shareDescription);
void requestInteractiveBugReport();
+ void requestBugReportWithExtraAttachment(in Uri extraAttachment);
void requestFullBugReport();
void requestRemoteBugReport(long nonce);
boolean launchBugReportHandlerApp();
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 251e4e8..a64261a 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -119,7 +119,8 @@
void scheduleSuicide();
void dispatchPackageBroadcast(int cmd, in String[] packages);
void scheduleCrash(in String msg, int typeId, in Bundle extras);
- void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, in String path,
+ void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc,
+ in String dumpBitmaps, in String path,
in ParcelFileDescriptor fd, in RemoteCallback finishCallback);
void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix,
in String[] args);
diff --git a/core/java/android/companion/ObservingDevicePresenceRequest.java b/core/java/android/companion/ObservingDevicePresenceRequest.java
index f1d594e..11ea735 100644
--- a/core/java/android/companion/ObservingDevicePresenceRequest.java
+++ b/core/java/android/companion/ObservingDevicePresenceRequest.java
@@ -183,7 +183,11 @@
* @param uuid The ParcelUuid for observing device presence.
*/
@NonNull
- @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE,
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_SCAN
+ })
public Builder setUuid(@NonNull ParcelUuid uuid) {
checkNotUsed();
this.mUuid = uuid;
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 9159929..1d4403c 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -887,7 +887,7 @@
* <p> Setting this property to true will enable the user's CE storage to remain unlocked when
* the user is stopped using
* {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int,
- * boolean, IStopUserCallback)}.
+ * IStopUserCallback)}.
*
* <p> When this property is false, delayed locking may still be applicable at a global
* level for all users via the {@code config_multiuserDelayUserDataLocking}. That is, delayed
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index 625d7cb..c7237ea 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -58,6 +58,16 @@
synchronized (LOOKUP_TABLES_WRITE_LOCK) {
putInto(
sLookupTables,
+ /* scaleKey= */ 1.05f,
+ new FontScaleConverterImpl(
+ /* fromSp= */
+ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */
+ new float[] { 8.4f, 10.5f, 12.6f, 14.8f, 18.6f, 20.6f, 24.4f, 30f, 100})
+ );
+
+ putInto(
+ sLookupTables,
/* scaleKey= */ 1.1f,
new FontScaleConverterImpl(
/* fromSp= */
@@ -78,6 +88,16 @@
putInto(
sLookupTables,
+ /* scaleKey= */ 1.2f,
+ new FontScaleConverterImpl(
+ /* fromSp= */
+ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */
+ new float[] { 9.6f, 12f, 14.4f, 17.2f, 20.4f, 22.4f, 25.6f, 30f, 100})
+ );
+
+ putInto(
+ sLookupTables,
/* scaleKey= */ 1.3f,
new FontScaleConverterImpl(
/* fromSp= */
@@ -117,7 +137,7 @@
);
}
- sMinScaleBeforeCurvesApplied = getScaleFromKey(sLookupTables.keyAt(0)) - 0.02f;
+ sMinScaleBeforeCurvesApplied = getScaleFromKey(sLookupTables.keyAt(0)) - 0.01f;
if (sMinScaleBeforeCurvesApplied <= 1.0f) {
throw new IllegalStateException(
"You should only apply non-linear scaling to font scales > 1"
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 2816f77..1c37aa2 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -94,33 +94,8 @@
// Keyboard layouts configuration.
KeyboardLayout[] getKeyboardLayouts();
- KeyboardLayout[] getKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
-
KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
- String getCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier);
-
- @EnforcePermission("SET_KEYBOARD_LAYOUT")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
- void setCurrentKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor);
-
- String[] getEnabledKeyboardLayoutsForInputDevice(in InputDeviceIdentifier identifier);
-
- @EnforcePermission("SET_KEYBOARD_LAYOUT")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
- void addKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor);
-
- @EnforcePermission("SET_KEYBOARD_LAYOUT")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
- void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor);
-
- // New Keyboard layout config APIs
KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo,
in InputMethodSubtype imeSubtype);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a1242fb..f949158 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -473,22 +473,21 @@
}
/**
- * Returns the descriptors of all supported keyboard layouts appropriate for the specified
- * input device.
+ * Returns the descriptors of all supported keyboard layouts.
* <p>
* The input manager consults the built-in keyboard layouts as well as all keyboard layouts
* advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
* </p>
*
- * @param device The input device to query.
* @return The ids of all keyboard layouts which are supported by the specified input device.
*
* @hide
*/
@TestApi
@NonNull
- public List<String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull InputDevice device) {
- KeyboardLayout[] layouts = getKeyboardLayoutsForInputDevice(device.getIdentifier());
+ @SuppressLint("UnflaggedApi")
+ public List<String> getKeyboardLayoutDescriptors() {
+ KeyboardLayout[] layouts = getKeyboardLayouts();
List<String> res = new ArrayList<>();
for (KeyboardLayout kl : layouts) {
res.add(kl.getDescriptor());
@@ -511,33 +510,18 @@
@TestApi
@NonNull
public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String layoutDescriptor) {
- KeyboardLayout[] layouts = getKeyboardLayouts();
- for (KeyboardLayout kl : layouts) {
- if (layoutDescriptor.equals(kl.getDescriptor())) {
- return kl.getLayoutType();
- }
- }
- return "";
+ KeyboardLayout layout = getKeyboardLayout(layoutDescriptor);
+ return layout == null ? "" : layout.getLayoutType();
}
/**
- * Gets information about all supported keyboard layouts appropriate
- * for a specific input device.
- * <p>
- * The input manager consults the built-in keyboard layouts as well
- * as all keyboard layouts advertised by applications using a
- * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
- * </p>
- *
- * @return A list of all supported keyboard layouts for a specific
- * input device.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
@NonNull
public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
@NonNull InputDeviceIdentifier identifier) {
- return mGlobal.getKeyboardLayoutsForInputDevice(identifier);
+ return new KeyboardLayout[0];
}
/**
@@ -549,6 +533,7 @@
*
* @hide
*/
+ @Nullable
public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
if (keyboardLayoutDescriptor == null) {
throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
@@ -562,121 +547,45 @@
}
/**
- * Gets the current keyboard layout descriptor for the specified input device.
- *
- * @param identifier Identifier for the input device
- * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @TestApi
@Nullable
public String getCurrentKeyboardLayoutForInputDevice(
@NonNull InputDeviceIdentifier identifier) {
- try {
- return mIm.getCurrentKeyboardLayoutForInputDevice(identifier);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return null;
}
/**
- * Sets the current keyboard layout descriptor for the specified input device.
- * <p>
- * This method may have the side-effect of causing the input device in question to be
- * reconfigured.
- * </p>
- *
- * @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @TestApi
- @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void setCurrentKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
- @NonNull String keyboardLayoutDescriptor) {
- mGlobal.setCurrentKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
+ @NonNull String keyboardLayoutDescriptor) {}
/**
- * Gets all keyboard layout descriptors that are enabled for the specified input device.
- *
- * @param identifier The identifier for the input device.
- * @return The keyboard layout descriptors.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
- if (identifier == null) {
- throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
- }
-
- try {
- return mIm.getEnabledKeyboardLayoutsForInputDevice(identifier);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return new String[0];
}
/**
- * Adds the keyboard layout descriptor for the specified input device.
- * <p>
- * This method may have the side-effect of causing the input device in question to be
- * reconfigured.
- * </p>
- *
- * @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor) {
- if (identifier == null) {
- throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
- }
- if (keyboardLayoutDescriptor == null) {
- throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
- }
-
- try {
- mIm.addKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
}
/**
- * Removes the keyboard layout descriptor for the specified input device.
- * <p>
- * This method may have the side-effect of causing the input device in question to be
- * reconfigured.
- * </p>
- *
- * @param identifier The identifier for the input device.
- * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove.
- *
+ * TODO(b/330517633): Cleanup the unsupported API
* @hide
*/
- @TestApi
@RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void removeKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
@NonNull String keyboardLayoutDescriptor) {
- if (identifier == null) {
- throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
- }
- if (keyboardLayoutDescriptor == null) {
- throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
- }
-
- try {
- mIm.removeKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
}
/**
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 7c104a0..7b29666 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1068,36 +1068,21 @@
}
/**
- * @see InputManager#getKeyboardLayoutsForInputDevice(InputDeviceIdentifier)
+ * TODO(b/330517633): Cleanup the unsupported API
*/
@NonNull
public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
@NonNull InputDeviceIdentifier identifier) {
- try {
- return mIm.getKeyboardLayoutsForInputDevice(identifier);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
+ return new KeyboardLayout[0];
}
/**
- * @see InputManager#setCurrentKeyboardLayoutForInputDevice
- * (InputDeviceIdentifier, String)
+ * TODO(b/330517633): Cleanup the unsupported API
*/
- @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
public void setCurrentKeyboardLayoutForInputDevice(
@NonNull InputDeviceIdentifier identifier,
- @NonNull String keyboardLayoutDescriptor) {
- Objects.requireNonNull(identifier, "identifier must not be null");
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
- try {
- mIm.setCurrentKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- }
+ @NonNull String keyboardLayoutDescriptor) {}
+
/**
* @see InputDevice#getSensorManager()
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 2cd7aeb..cbfc5d1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -56,6 +56,7 @@
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
+import static android.view.inputmethod.Flags.ctrlShiftShortcut;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -400,9 +401,14 @@
private long mStylusHwSessionsTimeout = STYLUS_HANDWRITING_IDLE_TIMEOUT_MS;
private Runnable mStylusWindowIdleTimeoutRunnable;
private long mStylusWindowIdleTimeoutForTest;
- /** Tracks last {@link MotionEvent#getToolType(int)} used for {@link MotionEvent#ACTION_DOWN}.
+ /**
+ * Tracks last {@link MotionEvent#getToolType(int)} used for {@link MotionEvent#ACTION_DOWN}.
**/
private int mLastUsedToolType;
+ /**
+ * Tracks the ctrl+shift shortcut
+ **/
+ private boolean mUsingCtrlShiftShortcut = false;
/**
* Returns whether {@link InputMethodService} is responsible for rendering the back button and
@@ -3612,7 +3618,8 @@
// any KeyEvent keyDown should reset last toolType.
updateEditorToolTypeInternal(MotionEvent.TOOL_TYPE_UNKNOWN);
}
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
final ExtractEditText eet = getExtractEditTextIfVisible();
if (eet != null && eet.handleBackInTextActionModeIfNeeded(event)) {
return true;
@@ -3622,14 +3629,33 @@
return true;
}
return false;
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE && KeyEvent.metaStateHasModifiers(
+ } else if (keyCode == KeyEvent.KEYCODE_SPACE && KeyEvent.metaStateHasModifiers(
event.getMetaState() & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) {
if (mDecorViewVisible && mWindowVisible) {
int direction = (event.getMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
mPrivOps.switchKeyboardLayoutAsync(direction);
+ event.startTracking();
return true;
}
}
+
+ // Check if this may be a ctrl+shift shortcut
+ if (ctrlShiftShortcut()) {
+ if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
+ || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
+ // Potentially Ctrl+Shift shortcut if Ctrl is currently pressed
+ mUsingCtrlShiftShortcut = KeyEvent.metaStateHasModifiers(
+ event.getMetaState() & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON);
+ } else if (keyCode == KeyEvent.KEYCODE_CTRL_LEFT
+ || keyCode == KeyEvent.KEYCODE_CTRL_RIGHT) {
+ // Potentially Ctrl+Shift shortcut if Shift is currently pressed
+ mUsingCtrlShiftShortcut = KeyEvent.metaStateHasModifiers(
+ event.getMetaState() & ~KeyEvent.META_CTRL_MASK, KeyEvent.META_SHIFT_ON);
+ } else {
+ mUsingCtrlShiftShortcut = false;
+ }
+ }
+
return doMovementKey(keyCode, event, MOVEMENT_DOWN);
}
@@ -3671,7 +3697,27 @@
* them to perform navigation in the underlying application.
*/
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ if (ctrlShiftShortcut()) {
+ if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
+ || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT
+ || keyCode == KeyEvent.KEYCODE_CTRL_LEFT
+ || keyCode == KeyEvent.KEYCODE_CTRL_RIGHT) {
+ if (mUsingCtrlShiftShortcut
+ && event.hasNoModifiers()) {
+ mUsingCtrlShiftShortcut = false;
+ if (mDecorViewVisible && mWindowVisible) {
+ // Move to the next IME
+ switchToNextInputMethod(false /* onlyCurrentIme */);
+ // TODO(b/332937629): Make the event stream consistent again
+ return true;
+ }
+ }
+ } else {
+ mUsingCtrlShiftShortcut = false;
+ }
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
final ExtractEditText eet = getExtractEditTextIfVisible();
if (eet != null && eet.handleBackInTextActionModeIfNeeded(event)) {
return true;
@@ -3679,7 +3725,12 @@
if (event.isTracking() && !event.isCanceled()) {
return handleBack(true);
}
+ } else if (keyCode == KeyEvent.KEYCODE_SPACE) {
+ if (event.isTracking() && !event.isCanceled()) {
+ return true;
+ }
}
+
return doMovementKey(keyCode, event, MOVEMENT_UP);
}
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 2fe115f..5b711c9 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -25,8 +25,6 @@
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
-import dalvik.annotation.optimization.CriticalNative;
-
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -80,7 +78,6 @@
private native static void nativeDestroy(long ptr);
@UnsupportedAppUsage
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
- @CriticalNative
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 8a17742..bb74a3e 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -1436,10 +1436,10 @@
* @throws IOException if the recovery system service could not be contacted
*/
private boolean requestLskf(String packageName, IntentSender sender) throws IOException {
- Log.i(TAG, String.format("<%s> is requesting LSFK", packageName));
+ Log.i(TAG, TextUtils.formatSimple("Package<%s> requesting LSKF", packageName));
try {
boolean validRequest = mService.requestLskf(packageName, sender);
- Log.i(TAG, String.format("LSKF Request isValid = %b", validRequest));
+ Log.i(TAG, TextUtils.formatSimple("LSKF Request isValid = %b", validRequest));
return validRequest;
} catch (RemoteException | SecurityException e) {
throw new IOException("could not request LSKF capture", e);
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 3441244..fe3fa8c 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -240,6 +240,16 @@
public static final String EXTRA_PERMISSION_USAGES =
"android.permission.extra.PERMISSION_USAGES";
+ /**
+ * Specify what permissions are device aware. Only device aware permissions can be granted to
+ * a remote device.
+ * @hide
+ */
+ public static final Set<String> DEVICE_AWARE_PERMISSIONS =
+ Flags.deviceAwarePermissionsEnabled()
+ ? Set.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
+ : Collections.emptySet();
+
private final @NonNull Context mContext;
private final IPackageManager mPackageManager;
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 92bbadc..c26f351 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -157,3 +157,13 @@
bug: "266164193"
}
+flag {
+ name: "ignore_apex_permissions"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Ignore APEX pacakges for permissions on V+"
+ bug: "301320911"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0fc51e7..582c90f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -27,12 +27,15 @@
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents;
+import static com.android.window.flags.Flags.offloadColorExtraction;
import android.animation.AnimationHandler;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -832,6 +835,15 @@
}
/**
+ * Called when the dim amount of the wallpaper changed. This can be used to recompute the
+ * wallpaper colors based on the new dim, and call {@link #notifyColorsChanged()}.
+ * @hide
+ */
+ @FlaggedApi(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void onDimAmountChanged(float dimAmount) {
+ }
+
+ /**
* Called when an application has changed the desired virtual size of
* the wallpaper.
*/
@@ -1043,6 +1055,10 @@
}
mPreviousWallpaperDimAmount = mWallpaperDimAmount;
+
+ // after the dim changes, allow colors to be immediately recomputed
+ mLastColorInvalidation = 0;
+ if (offloadColorExtraction()) onDimAmountChanged(mWallpaperDimAmount);
}
/**
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 559fa96..a8a0c5b 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -53,6 +53,19 @@
}
flag {
+ name: "complete_font_load_in_system_services_ready"
+ namespace: "text"
+ description: "Fix to ensure that font loading is complete on system-services-ready boot phase."
+ # Make read only, as font loading is in the critical boot path which happens before the read-write
+ # flags propagate to the device.
+ is_fixed_read_only: true
+ bug: "327941215"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "phrase_strict_fallback"
namespace: "text"
description: "Feature flag for automatic fallback from phrase based line break to strict line break."
@@ -147,3 +160,14 @@
description: "Feature flag for showing error message when user tries stylus handwriting on a text field which doesn't support it"
bug: "297962571"
}
+
+flag {
+ name: "fix_font_update_failure"
+ namespace: "text"
+ description: "There was a bug of updating system font from Android 13 to 14. This flag for fixing the migration failure."
+ is_fixed_read_only: true
+ bug: "331717791"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index b300022..6caf4d6 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -196,11 +196,11 @@
if (!super.setControl(control, showTypes, hideTypes)) {
return false;
}
- final boolean hasLeash = control != null && control.getLeash() != null;
- if (!hasLeash && !mIsRequestedVisibleAwaitingLeash) {
+ if (control == null && !mIsRequestedVisibleAwaitingLeash) {
mController.setRequestedVisibleTypes(0 /* visibleTypes */, getType());
removeSurface();
}
+ final boolean hasLeash = control != null && control.getLeash() != null;
if (hasLeash) {
mIsRequestedVisibleAwaitingLeash = false;
}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 0ae3e59..56a24e4 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -3783,6 +3783,13 @@
throw new IllegalArgumentException(
"idBits must contain at least one pointer from this motion event");
}
+ final int currentBits = getPointerIdBits();
+ if ((currentBits & idBits) != idBits) {
+ throw new IllegalArgumentException(
+ "idBits must be a non-empty subset of the pointer IDs from this MotionEvent, "
+ + "got idBits: "
+ + String.format("0x%x", idBits) + " for " + this);
+ }
MotionEvent event = obtain();
event.mNativePtr = nativeSplit(event.mNativePtr, this.mNativePtr, idBits);
return event;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index bd8e9c6..eb7de93 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -226,6 +226,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -3334,16 +3335,17 @@
public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0x00000000;
/**
- * Live region mode specifying that accessibility services should announce
- * changes to this view.
+ * Live region mode specifying that accessibility services should notify users of changes to
+ * this view.
* <p>
* Use with {@link #setAccessibilityLiveRegion(int)}.
*/
public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 0x00000001;
/**
- * Live region mode specifying that accessibility services should interrupt
- * ongoing speech to immediately announce changes to this view.
+ * Live region mode specifying that accessibility services should immediately notify users of
+ * changes to this view. For example, a screen reader may interrupt ongoing speech to
+ * immediately announce these changes.
* <p>
* Use with {@link #setAccessibilityLiveRegion(int)}.
*/
@@ -8797,14 +8799,17 @@
*
* <p>
* When transitioning from one Activity to another, instead of using
- * setAccessibilityPaneTitle(), set a descriptive title for its window by using android:label
- * for the matching <activity> entry in your application’s manifest or updating the title at
- * runtime with{@link android.app.Activity#setTitle(CharSequence)}.
+ * {@code setAccessibilityPaneTitle()}, set a descriptive title for its window by using
+ * {@code android:label}
+ * for the matching Activity entry in your application's manifest or updating the title at
+ * runtime with {@link android.app.Activity#setTitle(CharSequence)}.
*
* <p>
+ * <aside>
* <b>Note:</b> Use
* {@link androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)}
- * for backwards-compatibility. </aside>
+ * for backwards-compatibility.
+ * </aside>
* @param accessibilityPaneTitle The pane's title. Setting to {@code null} indicates that this
* View is not a pane.
*
@@ -8912,7 +8917,7 @@
* They should not need to specify what exactly is announced to users.
*
* <p>
- * In general, only announce transitions and don’t generate a confirmation message for simple
+ * In general, only announce transitions and don't generate a confirmation message for simple
* actions like a button press. Label your controls concisely and precisely instead, and for
* significant UI changes like window changes, use
* {@link android.app.Activity#setTitle(CharSequence)} and
@@ -15305,33 +15310,56 @@
* to the view's content description or text, or to the content descriptions
* or text of the view's children (where applicable).
* <p>
- * To indicate that the user should be notified of changes, use
- * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. Announcements from this region are queued and
- * do not disrupt ongoing speech.
+ * Different priority levels are available:
+ * <ul>
+ * <li>
+ * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}:
+ * Indicates that updates to the region should be presented to the user. Suitable in most
+ * cases for prominent updates within app content that don't require the user's immediate
+ * attention.
+ * </li>
+ * <li>
+ * {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}: Indicates that updates to the region have
+ * the highest priority and should be presented to the user immediately. This may result
+ * in disruptive notifications from an accessibility service, which may potentially
+ * interrupt other feedback or user actions, so it should generally be used only for
+ * critical, time-sensitive information.
+ * </li>
+ * <li>
+ * {@link #ACCESSIBILITY_LIVE_REGION_NONE}: Disables change announcements (the default for
+ * most views).
+ * </li>
+ * </ul>
* <p>
- * For example, selecting an option in a dropdown menu may update a panel below with the updated
- * content. This panel may be marked as a live region with
- * {@link #ACCESSIBILITY_LIVE_REGION_POLITE} to notify users of the change.
+ * Examples:
+ * <ul>
+ * <li>
+ * Selecting an option in a dropdown menu updates a panel below with the updated
+ * content. This panel may be marked as a live region with
+ * {@link #ACCESSIBILITY_LIVE_REGION_POLITE} to notify users of the change. A screen
+ * reader may queue changes as announcements that don't disrupt ongoing speech.
+ * </li>
+ * <li>
+ * An emergency alert may be marked with {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}
+ * to immediately inform users of the emergency.
+ * </li>
+ * </ul>
* <p>
- * For notifying users about errors, such as in a login screen with text that displays an
- * "incorrect password" notification, that view should send an AccessibilityEvent of type
+ * For error notifications, like an "incorrect password" warning in a login screen, views
+ * should send a {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}
+ * {@code AccessibilityEvent} with a content change type
* {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set
- * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose
- * error-setting methods that support accessibility automatically. For example, instead of
- * explicitly sending this event when using a TextView, use
- * {@link android.widget.TextView#setError(CharSequence)}.
+ * {@link AccessibilityNodeInfo#setError(CharSequence)}. Custom widgets should provide
+ * error-setting methods that support accessibility. For example, use
+ * {@link android.widget.TextView#setError(CharSequence)} instead of explicitly sending events.
* <p>
- * To disable change notifications for this view, use
- * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. This is the default live region
- * mode for most views.
+ * Don't use live regions for frequently-updating UI elements (e.g., progress bars), as this can
+ * overwhelm the user with feedback from accessibility services. If necessary, use
+ * {@link AccessibilityNodeInfo#setMinDurationBetweenContentChanges(Duration)} to throttle
+ * feedback and reduce disruptions.
* <p>
- * If the view's changes should interrupt ongoing speech and notify the user
- * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}. This may result in disruptive
- * announcements from an accessibility service, so it should generally be used only to convey
- * information that is time-sensitive or critical for use of the application. Examples may
- * include an incoming call or an emergency alert.
- * <p>
- * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#setAccessibilityLiveRegion(View, int)}
+ * <aside><b>Note:</b> Use
+ * {@link androidx.core.view.ViewCompat#setAccessibilityLiveRegion(View, int)}
* for backwards-compatibility. </aside>
*
* @param mode The live region mode for this view, one of:
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index db1b73f..c5a4d67 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6384,6 +6384,12 @@
return "MSG_KEEP_CLEAR_RECTS_CHANGED";
case MSG_REFRESH_POINTER_ICON:
return "MSG_REFRESH_POINTER_ICON";
+ case MSG_TOUCH_BOOST_TIMEOUT:
+ return "MSG_TOUCH_BOOST_TIMEOUT";
+ case MSG_CHECK_INVALIDATION_IDLE:
+ return "MSG_CHECK_INVALIDATION_IDLE";
+ case MSG_FRAME_RATE_SETTING:
+ return "MSG_FRAME_RATE_SETTING";
}
return super.getMessageName(message);
}
@@ -9581,6 +9587,8 @@
}
mRemoved = true;
mOnBackInvokedDispatcher.detachFromWindow();
+ removeVrrMessages();
+
if (mAdded) {
dispatchDetachedFromWindow();
}
@@ -12545,8 +12553,8 @@
if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
frameRateCategory, false).applyAsyncUnsafe();
- mLastPreferredFrameRateCategory = frameRateCategory;
}
+ mLastPreferredFrameRateCategory = frameRateCategory;
}
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate category", e);
@@ -12606,8 +12614,8 @@
if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
mFrameRateCompatibility).applyAsyncUnsafe();
- mLastPreferredFrameRate = preferredFrameRate;
}
+ mLastPreferredFrameRate = preferredFrameRate;
}
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate", e);
@@ -12849,4 +12857,10 @@
mHasIdledMessage = true;
}
}
+
+ private void removeVrrMessages() {
+ mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
+ mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE);
+ mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 03ba8ae..a5ba294 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -159,7 +159,7 @@
* <p> To avoid disconnected trees, this flag will also prefetch the parent. Siblings will be
* prefetched before descendants.
*
- * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ * <p> See {@link #FLAG_PREFETCH_ANCESTORS} for information on where these flags can be used.
*/
public static final int FLAG_PREFETCH_SIBLINGS = 1 << 1;
@@ -171,7 +171,7 @@
* {@link #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST} or this will trigger an
* IllegalArgumentException.
*
- * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ * <p> See {@link #FLAG_PREFETCH_ANCESTORS} for information on where these flags can be used.
*/
public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 1 << 2;
@@ -181,7 +181,7 @@
* {@link #FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST} or this will trigger an
* IllegalArgumentException.
*
- * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ * <p> See {@link #FLAG_PREFETCH_ANCESTORS} for information on where these flags can be used.
*/
public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 1 << 3;
@@ -191,7 +191,7 @@
* {@link #FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST} or this will trigger an
* IllegalArgumentException.
*
- * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ * <p> See {@link #FLAG_PREFETCH_ANCESTORS} for information on where these flags can be used.
*/
public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 1 << 4;
@@ -199,7 +199,7 @@
* Prefetching flag that specifies prefetching should not be interrupted by a request to
* retrieve a node or perform an action on a node.
*
- * @see #FLAG_PREFETCH_ANCESTORS for where to use these flags.
+ * <p> See {@link #FLAG_PREFETCH_ANCESTORS} for information on where these flags can be used.
*/
public static final int FLAG_PREFETCH_UNINTERRUPTIBLE = 1 << 5;
@@ -1295,6 +1295,8 @@
/**
* Get the child at given index.
*
+ * <p>
+ * See {@link #getParent(int)} for a description of prefetching.
* @param index The child index.
* @param prefetchingStrategy the prefetching strategy.
* @return The child node.
@@ -1302,7 +1304,6 @@
* @throws IllegalStateException If called outside of an {@link AccessibilityService} and before
* calling {@link #setQueryFromAppProcessEnabled}.
*
- * @see AccessibilityNodeInfo#getParent(int) for a description of prefetching.
*/
@Nullable
public AccessibilityNodeInfo getChild(int index, @PrefetchingStrategy int prefetchingStrategy) {
@@ -1903,8 +1904,13 @@
* Accessibility service will throttle those content change events and only handle one event
* per minute for that view.
* </p>
+ * <p>
+ * Example UI elements that frequently update and may benefit from a duration are progress bars,
+ * timers, and stopwatches.
+ * </p>
*
- * @see AccessibilityEvent#getContentChangeTypes for all content change types.
+ * @see AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED
+ * @see AccessibilityEvent#getContentChangeTypes
* @param duration the minimum duration between content change events.
* Negative duration would be treated as zero.
*/
@@ -3954,7 +3960,7 @@
/**
* Returns the container title.
*
- * @see #setContainerTitle for details.
+ * @see #setContainerTitle
*/
@Nullable
public CharSequence getContainerTitle() {
@@ -5187,8 +5193,8 @@
* <p>The node that is focused should return {@code true} for
* {@link AccessibilityNodeInfo#isFocused()}.
*
- * @see #ACTION_ACCESSIBILITY_FOCUS for the difference between system and accessibility
- * focus.
+ * See {@link #ACTION_ACCESSIBILITY_FOCUS} for the difference between system and
+ * accessibility focus.
*/
public static final AccessibilityAction ACTION_FOCUS =
new AccessibilityAction(AccessibilityNodeInfo.ACTION_FOCUS);
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 80b2396..8174da6 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1153,6 +1153,9 @@
}
final boolean startInput;
synchronized (mH) {
+ if (reason == UnbindReason.DISCONNECT_IME) {
+ mImeDispatcher.clear();
+ }
if (getBindSequenceLocked() != sequence) {
return;
}
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 4c3a290..d79903b 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -94,3 +94,15 @@
bug: "322836622"
is_fixed_read_only: true
}
+
+flag {
+ name: "ctrl_shift_shortcut"
+ namespace: "input_method"
+ description: "Ctrl+Shift shortcut to switch IMEs"
+ bug: "327198899"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 356d059..6e43d0f 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -69,6 +69,7 @@
import com.android.internal.R;
import java.text.NumberFormat;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Locale;
@@ -139,6 +140,14 @@
* <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
* if your application uses a light colored theme (a white background).</p>
*
+ * <h4>Accessibility</h4>
+ * <p>
+ * Consider using
+ * {@link AccessibilityNodeInfo#setMinDurationBetweenContentChanges(Duration)} to
+ * convey to accessibility services that changes can be throttled. This may reduce the
+ * frequency of potentially disruptive notifications.
+ * </p>
+ *
* <p><strong>XML attributes</b></strong>
* <p>
* See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 42c2d80..b5bf529 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -568,7 +568,7 @@
handled = pageScroll(View.FOCUS_DOWN);
break;
case KeyEvent.KEYCODE_SPACE:
- pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
+ handled = pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);
break;
}
}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index efe31ff..616004b 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -36,3 +36,10 @@
description: "Enables a limit on the number of Tasks shown in Desktop Mode"
bug: "332502912"
}
+
+flag {
+ name: "enable_windowing_edge_drag_resize"
+ namespace: "lse_desktop_experience"
+ description: "Enables edge drag resizing for all input sources"
+ bug: "323383067"
+}
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index aa92af2..150b04e 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -21,4 +21,14 @@
namespace: "systemui"
description: "Prevent the system from sending consecutive onVisibilityChanged(false) events."
bug: "285631818"
+}
+
+flag {
+ name: "offload_color_extraction"
+ namespace: "systemui"
+ description: "Let ImageWallpaper take care of its wallpaper color extraction, instead of system_server"
+ bug: "328791519"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index dd6b772a..87c47da 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -128,9 +128,28 @@
}
flag {
+ name: "fifo_priority_for_major_ui_processes"
+ namespace: "windowing_frontend"
+ description: "Use realtime priority for SystemUI and launcher"
+ bug: "288140556"
+ is_fixed_read_only: true
+}
+
+flag {
name: "insets_decoupled_configuration"
namespace: "windowing_frontend"
description: "Configuration decoupled from insets"
bug: "151861875"
is_fixed_read_only: true
+}
+
+flag {
+ name: "keyguard_appear_transition"
+ namespace: "windowing_frontend"
+ description: "Add transition when keyguard appears"
+ bug: "327970608"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index a0c405e..ddb8ee0 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -24,8 +24,10 @@
import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
import static com.android.internal.util.ArrayUtils.convertToLongArray;
+import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.AlertDialog;
@@ -53,6 +55,7 @@
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import android.widget.Toast;
import com.android.internal.R;
@@ -328,11 +331,14 @@
warningToast.show();
}
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
private AlertDialog createShortcutWarningDialog(int userId) {
List<AccessibilityTarget> targets = getTargets(mContext, HARDWARE);
if (targets.size() == 0) {
return null;
}
+ final AccessibilityManager am = mFrameworkObjectProvider
+ .getAccessibilityManagerInstance(mContext);
// Avoid non-a11y users accidentally turning shortcut on without reading this carefully.
// Put "don't turn on" as the primary action.
@@ -360,10 +366,15 @@
// to the Settings.
final ComponentName configDefaultService =
ComponentName.unflattenFromString(defaultService);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
- configDefaultService.flattenToString(),
- userId);
+ if (Flags.a11yQsShortcut()) {
+ am.enableShortcutsForTargets(true, HARDWARE,
+ Set.of(configDefaultService.flattenToString()), userId);
+ } else {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ configDefaultService.flattenToString(),
+ userId);
+ }
}
})
.setPositiveButton(R.string.accessibility_shortcut_off,
@@ -373,13 +384,16 @@
mContext,
HARDWARE,
userId);
-
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
- userId);
- ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
- mContext, targetServices, userId);
-
+ if (Flags.a11yQsShortcut()) {
+ am.enableShortcutsForTargets(
+ false, HARDWARE, targetServices, userId);
+ } else {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "",
+ userId);
+ ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState(
+ mContext, targetServices, userId);
+ }
// If canceled, treat as if the dialog has never been shown
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index ba1dffc..f088592 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -23,11 +23,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -35,6 +38,8 @@
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.Set;
+
/**
* Abstract base class for creating various target related to accessibility service, accessibility
* activity, and allowlisting features.
@@ -108,13 +113,21 @@
}
}
+ @SuppressLint("MissingPermission")
@Override
public void onCheckedChanged(boolean isChecked) {
setShortcutEnabled(isChecked);
- if (isChecked) {
- optInValueToSettings(getContext(), getShortcutType(), getId());
+ if (Flags.a11yQsShortcut()) {
+ final AccessibilityManager am =
+ getContext().getSystemService(AccessibilityManager.class);
+ am.enableShortcutsForTargets(
+ isChecked, getShortcutType(), Set.of(mId), UserHandle.myUserId());
} else {
- optOutValueFromSettings(getContext(), getShortcutType(), getId());
+ if (isChecked) {
+ optInValueToSettings(getContext(), getShortcutType(), getId());
+ } else {
+ optOutValueFromSettings(getContext(), getShortcutType(), getId());
+ }
}
}
diff --git a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
index 7535979..f9efb50 100644
--- a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
@@ -17,17 +17,11 @@
package com.android.internal.accessibility.dialog;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
-import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
-import android.content.ComponentName;
import android.content.Context;
-import android.widget.Toast;
-import com.android.internal.R;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
@@ -47,30 +41,10 @@
@Override
public void onCheckedChanged(boolean isChecked) {
- switch (getShortcutType()) {
- case SOFTWARE:
- onCheckedFromAccessibilityButton(isChecked);
- return;
- case HARDWARE:
- super.onCheckedChanged(isChecked);
- return;
- default:
- throw new IllegalStateException("Unexpected shortcut type");
- }
- }
-
- private void onCheckedFromAccessibilityButton(boolean isChecked) {
- setShortcutEnabled(isChecked);
- final ComponentName componentName = ComponentName.unflattenFromString(getId());
- setAccessibilityServiceState(getContext(), componentName, isChecked);
-
- if (!isChecked) {
- optOutValueFromSettings(getContext(), UserShortcutType.HARDWARE, getId());
-
- final String warningText =
- getContext().getString(R.string.accessibility_uncheck_legacy_item_warning,
- getLabel());
- Toast.makeText(getContext(), warningText, Toast.LENGTH_SHORT).show();
+ if (getShortcutType() == HARDWARE) {
+ super.onCheckedChanged(isChecked);
+ } else {
+ throw new IllegalStateException("Unexpected shortcut type");
}
}
}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 6bd273b..4f55441 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -28,6 +28,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER;
+import static com.android.internal.app.ResolverActivity.EXTRA_RESTRICT_TO_SINGLE_USER;
import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE;
import android.annotation.Nullable;
@@ -190,7 +191,7 @@
.thenApplyAsync(targetResolveInfo -> {
if (isResolverActivityResolveInfo(targetResolveInfo)) {
launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
- callingUserId, targetUserId);
+ callingUserId, targetUserId, false);
// When switching to the personal profile, automatically start the activity
} else if (className.equals(FORWARD_INTENT_TO_PARENT)) {
startActivityAsCaller(newIntent, targetUserId);
@@ -218,7 +219,7 @@
.thenAcceptAsync(targetResolveInfo -> {
if (isResolverActivityResolveInfo(targetResolveInfo)) {
launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
- callingUserId, targetUserId);
+ callingUserId, targetUserId, true);
} else {
maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent,
targetUserId);
@@ -490,7 +491,7 @@
}
private void launchResolverActivityWithCorrectTab(Intent intentReceived, String className,
- Intent newIntent, int callingUserId, int targetUserId) {
+ Intent newIntent, int callingUserId, int targetUserId, boolean singleTabOnly) {
// When showing the intent resolver, instead of forwarding to the other profile,
// we launch it in the current user and select the other tab. This fixes b/155874820.
//
@@ -505,6 +506,9 @@
sanitizeIntent(intentReceived);
intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile);
intentReceived.putExtra(EXTRA_CALLING_USER, UserHandle.of(callingUserId));
+ if (singleTabOnly) {
+ intentReceived.putExtra(EXTRA_RESTRICT_TO_SINGLE_USER, true);
+ }
startActivityAsCaller(intentReceived, null, false, userId);
finish();
}
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 2096ba4..d244874 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -66,6 +66,7 @@
private final TraceBuffer mBuffer;
private final LegacyProtoLogViewerConfigReader mViewerConfig;
private final TreeMap<String, IProtoLogGroup> mLogGroups;
+ private final Runnable mCacheUpdater;
private final int mPerChunkSize;
private boolean mProtoLogEnabled;
@@ -73,20 +74,21 @@
private final Object mProtoLogEnabledLock = new Object();
public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename,
- TreeMap<String, IProtoLogGroup> logGroups) {
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
this(new File(outputFile), viewerConfigFilename, BUFFER_CAPACITY,
- new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups);
+ new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups, cacheUpdater);
}
public LegacyProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize,
- TreeMap<String, IProtoLogGroup> logGroups) {
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
mLogFile = file;
mBuffer = new TraceBuffer(bufferCapacity);
mLegacyViewerConfigFilename = viewerConfigFilename;
mViewerConfig = viewerConfig;
mPerChunkSize = perChunkSize;
- this.mLogGroups = logGroups;
+ mLogGroups = logGroups;
+ mCacheUpdater = cacheUpdater;
}
/**
@@ -285,6 +287,8 @@
return -1;
}
}
+
+ mCacheUpdater.run();
return 0;
}
@@ -399,5 +403,12 @@
public int stopLoggingToLogcat(String[] groups, ILogger logger) {
return setLogging(true /* setTextLogging */, false, logger, groups);
}
+
+ @Override
+ public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
+ // In legacy logging we just enable an entire group at a time without more granular control,
+ // so we ignore the level argument to this function.
+ return group.isLogToLogcat() || (group.isLogToProto() && isProtoEnabled());
+ }
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 561ca21..9f3ce81 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -52,6 +52,7 @@
import android.tracing.perfetto.InitArguments;
import android.tracing.perfetto.Producer;
import android.tracing.perfetto.TracingContext;
+import android.util.ArrayMap;
import android.util.LongArray;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
@@ -70,6 +71,7 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -83,18 +85,22 @@
private final AtomicInteger mTracingInstances = new AtomicInteger();
private final ProtoLogDataSource mDataSource = new ProtoLogDataSource(
- this.mTracingInstances::incrementAndGet,
+ this::onTracingInstanceStart,
this::dumpTransitionTraceConfig,
- this.mTracingInstances::decrementAndGet
+ this::onTracingInstanceStop
);
private final ProtoLogViewerConfigReader mViewerConfigReader;
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
private final TreeMap<String, IProtoLogGroup> mLogGroups;
+ private final Runnable mCacheUpdater;
+
+ private final Map<LogLevel, Integer> mDefaultLogLevelCounts = new ArrayMap<>();
+ private final Map<IProtoLogGroup, Map<LogLevel, Integer>> mLogLevelCounts = new ArrayMap<>();
private final ExecutorService mBackgroundLoggingService = Executors.newCachedThreadPool();
public PerfettoProtoLogImpl(String viewerConfigFilePath,
- TreeMap<String, IProtoLogGroup> logGroups) {
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
this(() -> {
try {
return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
@@ -102,28 +108,32 @@
Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
return null;
}
- }, logGroups);
+ }, logGroups, cacheUpdater);
}
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- TreeMap<String, IProtoLogGroup> logGroups
+ TreeMap<String, IProtoLogGroup> logGroups,
+ Runnable cacheUpdater
) {
this(viewerConfigInputStreamProvider,
- new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups);
+ new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups,
+ cacheUpdater);
}
@VisibleForTesting
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
ProtoLogViewerConfigReader viewerConfigReader,
- TreeMap<String, IProtoLogGroup> logGroups
+ TreeMap<String, IProtoLogGroup> logGroups,
+ Runnable cacheUpdater
) {
Producer.init(InitArguments.DEFAULTS);
mDataSource.register(DataSourceParams.DEFAULTS);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
this.mLogGroups = logGroups;
+ this.mCacheUpdater = cacheUpdater;
}
/**
@@ -494,6 +504,29 @@
return setTextLogging(false, logger, groups);
}
+ @Override
+ public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
+ return group.isLogToLogcat() || getLogFromLevel(group).ordinal() <= level.ordinal();
+ }
+
+ private LogLevel getLogFromLevel(IProtoLogGroup group) {
+ if (mLogLevelCounts.containsKey(group)) {
+ for (LogLevel logLevel : LogLevel.values()) {
+ if (mLogLevelCounts.get(group).getOrDefault(logLevel, 0) > 0) {
+ return logLevel;
+ }
+ }
+ } else {
+ for (LogLevel logLevel : LogLevel.values()) {
+ if (mDefaultLogLevelCounts.getOrDefault(logLevel, 0) > 0) {
+ return logLevel;
+ }
+ }
+ }
+
+ return LogLevel.WTF;
+ }
+
/**
* Start logging the stack trace of the when the log message happened for target groups
* @return status code
@@ -521,6 +554,8 @@
return -1;
}
}
+
+ mCacheUpdater.run();
return 0;
}
@@ -567,6 +602,61 @@
return -1;
}
+ private synchronized void onTracingInstanceStart(ProtoLogDataSource.ProtoLogConfig config) {
+ this.mTracingInstances.incrementAndGet();
+
+ final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
+ mDefaultLogLevelCounts.put(defaultLogFrom,
+ mDefaultLogLevelCounts.getOrDefault(defaultLogFrom, 0) + 1);
+
+ final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
+
+ for (String overriddenGroupTag : overriddenGroupTags) {
+ IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
+
+ mLogLevelCounts.putIfAbsent(group, new ArrayMap<>());
+ final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group);
+
+ final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
+ logLevelsCountsForGroup.put(logFromLevel,
+ logLevelsCountsForGroup.getOrDefault(logFromLevel, 0) + 1);
+ }
+
+ mCacheUpdater.run();
+ }
+
+ private synchronized void onTracingInstanceStop(ProtoLogDataSource.ProtoLogConfig config) {
+ this.mTracingInstances.decrementAndGet();
+
+ final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
+ mDefaultLogLevelCounts.put(defaultLogFrom,
+ mDefaultLogLevelCounts.get(defaultLogFrom) - 1);
+ if (mDefaultLogLevelCounts.get(defaultLogFrom) <= 0) {
+ mDefaultLogLevelCounts.remove(defaultLogFrom);
+ }
+
+ final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
+
+ for (String overriddenGroupTag : overriddenGroupTags) {
+ IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
+
+ mLogLevelCounts.putIfAbsent(group, new ArrayMap<>());
+ final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group);
+
+ final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
+ logLevelsCountsForGroup.put(logFromLevel,
+ logLevelsCountsForGroup.get(logFromLevel) - 1);
+ if (logLevelsCountsForGroup.get(logFromLevel) <= 0) {
+ logLevelsCountsForGroup.remove(logFromLevel);
+ }
+ if (logLevelsCountsForGroup.isEmpty()) {
+ mLogLevelCounts.remove(group);
+ }
+ }
+
+ mCacheUpdater.run();
+ }
+
static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
Slog.i(LOG_TAG, msg);
if (pw != null) {
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index a2d5e70..e79bf36 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -39,16 +39,19 @@
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
ProtoLogDataSource.TlsState,
ProtoLogDataSource.IncrementalState> {
- private final Runnable mOnStart;
+ private final Consumer<ProtoLogConfig> mOnStart;
private final Runnable mOnFlush;
- private final Runnable mOnStop;
+ private final Consumer<ProtoLogConfig> mOnStop;
- public ProtoLogDataSource(Runnable onStart, Runnable onFlush, Runnable onStop) {
+ public ProtoLogDataSource(Consumer<ProtoLogConfig> onStart, Runnable onFlush,
+ Consumer<ProtoLogConfig> onStop) {
super("android.protolog");
this.mOnStart = onStart;
this.mOnFlush = onFlush;
@@ -138,7 +141,7 @@
public boolean clearReported = false;
}
- private static class ProtoLogConfig {
+ public static class ProtoLogConfig {
private final LogLevel mDefaultLogFromLevel;
private final Map<String, GroupConfig> mGroupConfigs;
@@ -151,13 +154,17 @@
this.mGroupConfigs = groupConfigs;
}
- private GroupConfig getConfigFor(String groupTag) {
+ public GroupConfig getConfigFor(String groupTag) {
return mGroupConfigs.getOrDefault(groupTag, getDefaultGroupConfig());
}
- private GroupConfig getDefaultGroupConfig() {
+ public GroupConfig getDefaultGroupConfig() {
return new GroupConfig(mDefaultLogFromLevel, false);
}
+
+ public Set<String> getGroupTagsWithOverriddenConfigs() {
+ return mGroupConfigs.keySet();
+ }
}
public static class GroupConfig {
@@ -255,18 +262,18 @@
public static class Instance extends DataSourceInstance {
- private final Runnable mOnStart;
+ private final Consumer<ProtoLogConfig> mOnStart;
private final Runnable mOnFlush;
- private final Runnable mOnStop;
+ private final Consumer<ProtoLogConfig> mOnStop;
private final ProtoLogConfig mConfig;
public Instance(
DataSource<Instance, TlsState, IncrementalState> dataSource,
int instanceIdx,
ProtoLogConfig config,
- Runnable onStart,
+ Consumer<ProtoLogConfig> onStart,
Runnable onFlush,
- Runnable onStop
+ Consumer<ProtoLogConfig> onStop
) {
super(dataSource, instanceIdx);
this.mOnStart = onStart;
@@ -277,7 +284,7 @@
@Override
public void onStart(StartCallbackArguments args) {
- this.mOnStart.run();
+ this.mOnStart.accept(this.mConfig);
}
@Override
@@ -287,7 +294,7 @@
@Override
public void onStop(StopCallbackArguments args) {
- this.mOnStop.run();
+ this.mOnStop.accept(this.mConfig);
}
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 487ae814..6d142af 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -16,6 +16,7 @@
package com.android.internal.protolog;
+import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.CACHE_UPDATER;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_OUTPUT_FILE_PATH;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_VIEWER_CONFIG_PATH;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LOG_GROUPS;
@@ -49,6 +50,9 @@
@ProtoLogToolInjected(LOG_GROUPS)
private static TreeMap<String, IProtoLogGroup> sLogGroups;
+ @ProtoLogToolInjected(CACHE_UPDATER)
+ private static Runnable sCacheUpdater;
+
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
public static void d(IProtoLogGroup group, long messageHash, int paramsMask,
@Nullable String messageString,
@@ -94,9 +98,12 @@
getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, messageString, args);
}
- public static boolean isEnabled(IProtoLogGroup group) {
- // TODO: Implement for performance reasons, with optional level parameter?
- return true;
+ /**
+ * Should return true iff we should be logging to either protolog or logcat for this group
+ * and log level.
+ */
+ public static boolean isEnabled(IProtoLogGroup group, LogLevel level) {
+ return getSingleInstance().isEnabled(group, level);
}
/**
@@ -105,11 +112,14 @@
public static synchronized IProtoLog getSingleInstance() {
if (sServiceInstance == null) {
if (android.tracing.Flags.perfettoProtologTracing()) {
- sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sLogGroups);
+ sServiceInstance = new PerfettoProtoLogImpl(
+ sViewerConfigPath, sLogGroups, sCacheUpdater);
} else {
sServiceInstance = new LegacyProtoLogImpl(
- sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups);
+ sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups, sCacheUpdater);
}
+
+ sCacheUpdater.run();
}
return sServiceInstance;
}
diff --git a/core/java/com/android/internal/protolog/common/IProtoLog.java b/core/java/com/android/internal/protolog/common/IProtoLog.java
index c06d14b..f72d9f7 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLog.java
@@ -52,4 +52,12 @@
* @return status code
*/
int stopLoggingToLogcat(String[] groups, ILogger logger);
+
+ /**
+ * Should return true iff logging is enabled to ProtoLog or to Logcat for this group and level.
+ * @param group ProtoLog group to check for.
+ * @param level ProtoLog level to check for.
+ * @return If we need to log this group and level to either ProtoLog or Logcat.
+ */
+ boolean isEnabled(IProtoLogGroup group, LogLevel level);
}
diff --git a/core/java/com/android/internal/protolog/common/IProtoLogGroup.java b/core/java/com/android/internal/protolog/common/IProtoLogGroup.java
index 4e9686f99..149aa7a 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLogGroup.java
@@ -38,6 +38,7 @@
/**
* returns true is any logging is enabled for this group.
+ * @deprecated TODO(b/324128613) remove once we migrate fully to Perfetto
*/
default boolean isLogToAny() {
return isLogToLogcat() || isLogToProto();
@@ -50,6 +51,7 @@
/**
* set binary logging for this group.
+ * @deprecated TODO(b/324128613) remove once we migrate fully to Perfetto
*/
void setLogToProto(boolean logToProto);
diff --git a/core/java/com/android/internal/protolog/common/ProtoLog.java b/core/java/com/android/internal/protolog/common/ProtoLog.java
index 18e3f66..8149cd5 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLog.java
@@ -135,7 +135,7 @@
* @param group Group to check enable status of.
* @return true iff this is being logged.
*/
- public static boolean isEnabled(IProtoLogGroup group) {
+ public static boolean isEnabled(IProtoLogGroup group, LogLevel level) {
if (REQUIRE_PROTOLOGTOOL) {
throw new UnsupportedOperationException(
"ProtoLog calls MUST be processed with ProtoLogTool");
diff --git a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
index 17c82d7..2d39f3b 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
@@ -23,7 +23,11 @@
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface ProtoLogToolInjected {
enum Value {
- VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH, LOG_GROUPS
+ VIEWER_CONFIG_PATH,
+ LEGACY_OUTPUT_FILE_PATH,
+ LEGACY_VIEWER_CONFIG_PATH,
+ LOG_GROUPS,
+ CACHE_UPDATER
}
Value value();
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 593bdf0..163f32e 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -110,3 +110,6 @@
# PerformanceHintManager
per-file android_os_PerformanceHintManager.cpp = file:/ADPF_OWNERS
+
+# IF Tools
+per-file android_tracing_Perfetto* = file:platform/development:/tools/winscope/OWNERS
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index 9525605..30d9ea1 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -225,7 +225,7 @@
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
-static void android_os_MessageQueue_nativeWake(jlong ptr) {
+static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 7c976b7..b6bf617 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -36,43 +36,41 @@
return SYSTEM_HYPHENATOR_PREFIX + lowerLocale + SYSTEM_HYPHENATOR_SUFFIX;
}
-static std::pair<const uint8_t*, uint32_t> mmapPatternFile(const std::string& locale) {
+static const uint8_t* mmapPatternFile(const std::string& locale) {
const std::string hyFilePath = buildFileName(locale);
const int fd = open(hyFilePath.c_str(), O_RDONLY | O_CLOEXEC);
if (fd == -1) {
- return std::make_pair(nullptr, 0); // Open failed.
+ return nullptr; // Open failed.
}
struct stat st = {};
if (fstat(fd, &st) == -1) { // Unlikely to happen.
close(fd);
- return std::make_pair(nullptr, 0);
+ return nullptr;
}
void* ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0 /* offset */);
close(fd);
if (ptr == MAP_FAILED) {
- return std::make_pair(nullptr, 0);
+ return nullptr;
}
- return std::make_pair(reinterpret_cast<const uint8_t*>(ptr), st.st_size);
+ return reinterpret_cast<const uint8_t*>(ptr);
}
static void addHyphenatorWithoutPatternFile(const std::string& locale, int minPrefix,
int minSuffix) {
- minikin::addHyphenator(locale,
- minikin::Hyphenator::loadBinary(nullptr, 0, minPrefix, minSuffix,
- locale));
+ minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
+ nullptr, minPrefix, minSuffix, locale));
}
static void addHyphenator(const std::string& locale, int minPrefix, int minSuffix) {
- auto [ptr, size] = mmapPatternFile(locale);
+ const uint8_t* ptr = mmapPatternFile(locale);
if (ptr == nullptr) {
ALOGE("Unable to find pattern file or unable to map it for %s", locale.c_str());
return;
}
- minikin::addHyphenator(locale,
- minikin::Hyphenator::loadBinary(ptr, size, minPrefix, minSuffix,
- locale));
+ minikin::addHyphenator(locale, minikin::Hyphenator::loadBinary(
+ ptr, minPrefix, minSuffix, locale));
}
static void addHyphenatorAlias(const std::string& from, const std::string& to) {
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index b900fa6..b51f72d 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -11,7 +11,6 @@
# Frameworks
ogunwale@google.com
jjaggi@google.com
-kwekua@google.com
roosa@google.com
per-file package_item_info.proto = file:/PACKAGE_MANAGER_OWNERS
per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 6cc5485..8072d69 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -16,6 +16,8 @@
package com.android.os.bugreports.tests;
+import static android.content.Context.RECEIVER_EXPORTED;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertNotNull;
@@ -358,7 +360,10 @@
// shell UID rather than our own.
BugreportBroadcastReceiver br = new BugreportBroadcastReceiver();
InstrumentationRegistry.getContext()
- .registerReceiver(br, new IntentFilter(INTENT_BUGREPORT_FINISHED));
+ .registerReceiver(
+ br,
+ new IntentFilter(INTENT_BUGREPORT_FINISHED),
+ RECEIVER_EXPORTED);
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
.executeShellCommand("am bug-report");
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index a2a5433..c7d5825 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -175,10 +175,12 @@
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(-1f)).isFalse()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0.85f)).isFalse()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.02f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.05f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.15f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.1499999f))
.isTrue()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.2f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.5f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(2f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(3f)).isTrue()
diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java
index c4c983d..bad0485 100644
--- a/core/tests/coretests/src/android/view/MotionEventTest.java
+++ b/core/tests/coretests/src/android/view/MotionEventTest.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
import android.graphics.Matrix;
import android.platform.test.annotations.Presubmit;
@@ -47,21 +48,25 @@
public class MotionEventTest {
private static final int ID_SOURCE_MASK = 0x3 << 30;
+ private PointerCoords pointerCoords(float x, float y) {
+ final var coords = new PointerCoords();
+ coords.x = x;
+ coords.y = y;
+ return coords;
+ }
+
+ private PointerProperties fingerProperties(int id) {
+ final var props = new PointerProperties();
+ props.id = id;
+ props.toolType = TOOL_TYPE_FINGER;
+ return props;
+ }
+
@Test
public void testObtainWithDisplayId() {
final int pointerCount = 1;
- PointerProperties[] properties = new PointerProperties[pointerCount];
- final PointerCoords[] coords = new PointerCoords[pointerCount];
- for (int i = 0; i < pointerCount; i++) {
- final PointerCoords c = new PointerCoords();
- c.x = i * 10;
- c.y = i * 20;
- coords[i] = c;
- final PointerProperties p = new PointerProperties();
- p.id = i;
- p.toolType = TOOL_TYPE_FINGER;
- properties[i] = p;
- }
+ final var properties = new PointerProperties[]{fingerProperties(0)};
+ final var coords = new PointerCoords[]{pointerCoords(10, 20)};
int displayId = 2;
MotionEvent motionEvent = MotionEvent.obtain(0, 0, ACTION_DOWN,
@@ -125,18 +130,8 @@
@Test
public void testCalculatesCursorPositionForMultiTouchMouseEvents() {
final int pointerCount = 2;
- final PointerProperties[] properties = new PointerProperties[pointerCount];
- final PointerCoords[] coords = new PointerCoords[pointerCount];
-
- for (int i = 0; i < pointerCount; ++i) {
- properties[i] = new PointerProperties();
- properties[i].id = i;
- properties[i].toolType = MotionEvent.TOOL_TYPE_FINGER;
-
- coords[i] = new PointerCoords();
- coords[i].x = 20 + i * 20;
- coords[i].y = 60 - i * 20;
- }
+ final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+ final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
0 /* eventTime */, ACTION_POINTER_DOWN, pointerCount, properties, coords,
@@ -238,4 +233,66 @@
assertEquals(10, (int) event.getX());
assertEquals(20, (int) event.getY());
}
+
+ @Test
+ public void testSplit() {
+ final int pointerCount = 2;
+ final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+ final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+ final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+ 0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+ 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+ 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+ 0 /* flags */);
+
+ final int idBits = ~0b1 & event.getPointerIdBits();
+ final MotionEvent splitEvent = event.split(idBits);
+ assertEquals(1, splitEvent.getPointerCount());
+ assertEquals(1, splitEvent.getPointerId(0));
+ assertEquals(40, (int) splitEvent.getX());
+ assertEquals(40, (int) splitEvent.getY());
+ }
+
+ @Test
+ public void testSplitFailsWhenNoIdsSpecified() {
+ final int pointerCount = 2;
+ final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+ final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+ final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+ 0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+ 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+ 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+ 0 /* flags */);
+
+ try {
+ final MotionEvent splitEvent = event.split(0);
+ fail("Splitting event with id bits 0 should throw: " + splitEvent);
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testSplitFailsWhenIdBitsDoNotMatch() {
+ final int pointerCount = 2;
+ final var properties = new PointerProperties[]{fingerProperties(0), fingerProperties(1)};
+ final var coords = new PointerCoords[]{pointerCoords(20, 60), pointerCoords(40, 40)};
+
+ final MotionEvent event = MotionEvent.obtain(0 /* downTime */,
+ 0 /* eventTime */, MotionEvent.ACTION_MOVE, pointerCount, properties, coords,
+ 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */,
+ 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_TOUCHSCREEN,
+ 0 /* flags */);
+
+ try {
+ final int idBits = 0b100;
+ final MotionEvent splitEvent = event.split(idBits);
+ fail("Splitting event with id bits that do not match any pointers should throw: "
+ + splitEvent);
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 365f348..5fab1a0 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -64,6 +64,9 @@
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.speech.tts.Voice;
@@ -71,6 +74,7 @@
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
import android.widget.Toast;
@@ -83,9 +87,11 @@
import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -93,11 +99,14 @@
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class AccessibilityShortcutControllerTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
private static final CharSequence PACKAGE_NAME_STRING = "Service name";
private static final String SERVICE_NAME_SUMMARY = "Summary";
@@ -126,6 +135,7 @@
private @Mock TextToSpeech mTextToSpeech;
private @Mock Voice mVoice;
private @Mock Ringtone mRingtone;
+ private @Captor ArgumentCaptor<List<String>> mListCaptor;
private MockContentResolver mContentResolver;
private WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
@@ -410,6 +420,7 @@
}
@Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
configureValidShortcutService();
@@ -423,6 +434,29 @@
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(SERVICE_NAME_STRING);
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void testClickingDisableButtonInDialog_shouldClearShortcutId_old() throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureValidShortcutService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
assertThat(
Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)
).isEmpty();
@@ -432,6 +466,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void turnOffVolumeShortcutForAlwaysOnA11yService_shouldTurnOffA11yService()
throws Exception {
configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
@@ -443,6 +479,8 @@
}
@Test
+ @EnableFlags(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE)
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void turnOffVolumeShortcutForAlwaysOnA11yService_hasOtherTypesShortcut_shouldNotTurnOffA11yService()
throws Exception {
configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
@@ -489,6 +527,7 @@
}
@Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn()
throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -503,15 +542,39 @@
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
- assertThat(
- Settings.Secure.getString(mContentResolver,
- ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(true), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(SERVICE_NAME_STRING);
assertThat(Settings.Secure.getInt(
mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
AccessibilityShortcutController.DialogStatus.SHOWN);
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn_old()
+ throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureDefaultAccessibilityService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+ assertThat(Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.SHOWN);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff()
throws Exception {
configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
@@ -526,9 +589,32 @@
captor.capture());
captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
- assertThat(
- Settings.Secure.getString(mContentResolver,
- ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false), eq(HARDWARE), mListCaptor.capture(), anyInt());
+ assertThat(mListCaptor.getValue()).isEmpty();
+ assertThat(Settings.Secure.getInt(
+ mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff_old()
+ throws Exception {
+ configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+ configureDefaultAccessibilityService();
+ Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+ AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+ getController().performAccessibilityShortcut();
+
+ ArgumentCaptor<DialogInterface.OnClickListener> captor =
+ ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+ verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
+ captor.capture());
+ captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+ assertThat(Settings.Secure.getString(mContentResolver,
+ ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
assertThat(Settings.Secure.getInt(
mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 2ea044c..a14d8e0 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -19,8 +19,10 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
@@ -31,8 +33,12 @@
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.Flags;
import android.view.accessibility.IAccessibilityManager;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -47,10 +53,13 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Collections;
+import java.util.List;
/**
* Unit Tests for
@@ -59,9 +68,13 @@
@RunWith(AndroidJUnit4.class)
public class InvisibleToggleAccessibilityServiceTargetTest {
@Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
@Mock
private IAccessibilityManager mAccessibilityManagerService;
+ @Captor
+ private ArgumentCaptor<List<String>> mListCaptor;
private static final String ALWAYS_ON_SERVICE_PACKAGE_LABEL = "always on a11y service";
private static final String ALWAYS_ON_SERVICE_COMPONENT_NAME =
@@ -104,6 +117,32 @@
}
@Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void onCheckedChanged_true_callA11yManagerToUpdateShortcuts() throws Exception {
+ mSut.onCheckedChanged(true);
+
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(true),
+ eq(ShortcutConstants.UserShortcutType.HARDWARE),
+ mListCaptor.capture(),
+ anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ public void onCheckedChanged_false_callA11yManagerToUpdateShortcuts() throws Exception {
+ mSut.onCheckedChanged(false);
+ verify(mAccessibilityManagerService).enableShortcutsForTargets(
+ eq(false),
+ eq(ShortcutConstants.UserShortcutType.HARDWARE),
+ mListCaptor.capture(),
+ anyInt());
+ assertThat(mListCaptor.getValue()).containsExactly(ALWAYS_ON_SERVICE_COMPONENT_NAME);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOnShortcut_hasOtherShortcut_serviceKeepsOn() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(
@@ -116,6 +155,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOnShortcut_noOtherShortcut_shouldTurnOnService() {
enableA11yService(/* enable= */ false);
addShortcutForA11yService(
@@ -128,6 +168,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOffShortcut_hasOtherShortcut_serviceKeepsOn() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(
@@ -140,6 +181,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
public void onCheckedChanged_turnOffShortcut_noOtherShortcut_shouldTurnOffService() {
enableA11yService(/* enable= */ true);
addShortcutForA11yService(
diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb
index 826adc39..97147a0 100644
--- a/data/etc/core.protolog.pb
+++ b/data/etc/core.protolog.pb
Binary files differ
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index d410d5f..6cf12de 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -709,18 +709,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
},
- "2959074735946674755": {
- "message": "Trying to update display configuration for system\/invalid process.",
- "level": "WARN",
- "group": "WM_DEBUG_CONFIGURATION",
- "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
- },
- "5668810920995272206": {
- "message": "Trying to update display configuration for invalid process, pid=%d",
- "level": "WARN",
- "group": "WM_DEBUG_CONFIGURATION",
- "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
- },
"-1123414663662718691": {
"message": "setVr2dDisplayId called for: %d",
"level": "DEBUG",
@@ -3469,6 +3457,12 @@
"group": "WM_DEBUG_WALLPAPER",
"at": "com\/android\/server\/wm\/WallpaperController.java"
},
+ "257349083882992098": {
+ "message": "updateWallpaperTokens requestedVisibility=%b on keyguardLocked=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WALLPAPER",
+ "at": "com\/android\/server\/wm\/WallpaperController.java"
+ },
"7408402065665963407": {
"message": "Wallpaper at display %d - visibility: %b, keyguardLocked: %b",
"level": "VERBOSE",
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index f9347ee..e8b4104 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -424,12 +424,14 @@
key 582 VOICE_ASSIST
# Linux KEY_ASSISTANT
key 583 ASSIST
+key 585 EMOJI_PICKER
key 656 MACRO_1
key 657 MACRO_2
key 658 MACRO_3
key 659 MACRO_4
# Keys defined by HID usages
+key usage 0x0c0065 SCREENSHOT FALLBACK_USAGE_MAPPING
key usage 0x0c0067 WINDOW FALLBACK_USAGE_MAPPING
key usage 0x0c006F BRIGHTNESS_UP FALLBACK_USAGE_MAPPING
key usage 0x0c0070 BRIGHTNESS_DOWN FALLBACK_USAGE_MAPPING
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 250362b..319f115 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -41,12 +41,15 @@
import libcore.util.NativeAllocationRegistry;
import java.io.IOException;
+import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.WeakHashMap;
public final class Bitmap implements Parcelable {
private static final String TAG = "Bitmap";
@@ -120,6 +123,11 @@
}
/**
+ * @hide
+ */
+ private static final WeakHashMap<Bitmap, Void> sAllBitmaps = new WeakHashMap<>();
+
+ /**
* Private constructor that must receive an already allocated native bitmap
* int (pointer).
*/
@@ -162,6 +170,9 @@
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
}
registry.registerNativeAllocation(this, nativeBitmap);
+ synchronized (Bitmap.class) {
+ sAllBitmaps.put(this, null);
+ }
}
/**
@@ -1510,6 +1521,86 @@
}
/**
+ * @hide
+ */
+ private static final class DumpData {
+ private int count;
+ private int format;
+ private long[] natives;
+ private byte[][] buffers;
+ private int max;
+
+ public DumpData(@NonNull CompressFormat format, int max) {
+ this.max = max;
+ this.format = format.nativeInt;
+ this.natives = new long[max];
+ this.buffers = new byte[max][];
+ this.count = 0;
+ }
+
+ public void add(long nativePtr, byte[] buffer) {
+ natives[count] = nativePtr;
+ buffers[count] = buffer;
+ count = (count >= max) ? max : count + 1;
+ }
+
+ public int size() {
+ return count;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ private static DumpData dumpData = null;
+
+
+ /**
+ * @hide
+ *
+ * Dump all the bitmaps with their contents compressed into dumpData
+ *
+ * @param format format of the compressed image, null to clear dump data
+ */
+ public static void dumpAll(@Nullable String format) {
+ if (format == null) {
+ /* release the dump data */
+ dumpData = null;
+ return;
+ }
+ final CompressFormat fmt;
+ if (format.equals("jpg") || format.equals("jpeg")) {
+ fmt = CompressFormat.JPEG;
+ } else if (format.equals("png")) {
+ fmt = CompressFormat.PNG;
+ } else if (format.equals("webp")) {
+ fmt = CompressFormat.WEBP_LOSSLESS;
+ } else {
+ Log.w(TAG, "No bitmaps dumped: unrecognized format " + format);
+ return;
+ }
+
+ final ArrayList<Bitmap> allBitmaps;
+ synchronized (Bitmap.class) {
+ allBitmaps = new ArrayList<>(sAllBitmaps.size());
+ for (Bitmap bitmap : sAllBitmaps.keySet()) {
+ if (bitmap != null && !bitmap.isRecycled()) {
+ allBitmaps.add(bitmap);
+ }
+ }
+ }
+
+ dumpData = new DumpData(fmt, allBitmaps.size());
+ for (Bitmap bitmap : allBitmaps) {
+ ByteArrayOutputStream bas = new ByteArrayOutputStream();
+ if (bitmap.compress(fmt, 90, bas)) {
+ dumpData.add(bitmap.getNativeInstance(), bas.toByteArray());
+ }
+ }
+ Log.i(TAG, dumpData.size() + "/" + allBitmaps.size() + " bitmaps dumped");
+ }
+
+ /**
* Number of bytes of temp storage we use for communicating between the
* native compressor and the java OutputStream.
*/
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 2cac2e1..2f2215f 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -17,7 +17,6 @@
package android.security;
import android.compat.annotation.UnsupportedAppUsage;
-import android.os.StrictMode;
/**
* This class provides some constants and helper methods related to Android's Keystore service.
@@ -38,17 +37,4 @@
public static KeyStore getInstance() {
return KEY_STORE;
}
-
- /**
- * Add an authentication record to the keystore authorization table.
- *
- * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster.
- * @return 0 on success, otherwise an error value corresponding to a
- * {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode.
- */
- public int addAuthToken(byte[] authToken) {
- StrictMode.noteDiskWrite();
-
- return Authorization.addAuthToken(authToken);
- }
}
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/KeyStoreAuthorization.java
similarity index 82%
rename from keystore/java/android/security/Authorization.java
rename to keystore/java/android/security/KeyStoreAuthorization.java
index 6404c4b..14d715f 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/KeyStoreAuthorization.java
@@ -33,15 +33,21 @@
* @hide This is the client side for IKeystoreAuthorization AIDL.
* It shall only be used by biometric authentication providers and Gatekeeper.
*/
-public class Authorization {
- private static final String TAG = "KeystoreAuthorization";
+public class KeyStoreAuthorization {
+ private static final String TAG = "KeyStoreAuthorization";
public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR;
+ private static final KeyStoreAuthorization sInstance = new KeyStoreAuthorization();
+
+ public static KeyStoreAuthorization getInstance() {
+ return sInstance;
+ }
+
/**
* @return an instance of IKeystoreAuthorization
*/
- public static IKeystoreAuthorization getService() {
+ private IKeystoreAuthorization getService() {
return IKeystoreAuthorization.Stub.asInterface(
ServiceManager.checkService("android.security.authorization"));
}
@@ -52,7 +58,7 @@
* @param authToken created by Android authenticators.
* @return 0 if successful or {@code ResponseCode.SYSTEM_ERROR}.
*/
- public static int addAuthToken(@NonNull HardwareAuthToken authToken) {
+ public int addAuthToken(@NonNull HardwareAuthToken authToken) {
StrictMode.noteSlowCall("addAuthToken");
try {
getService().addAuthToken(authToken);
@@ -70,7 +76,7 @@
* @param authToken
* @return 0 if successful or a {@code ResponseCode}.
*/
- public static int addAuthToken(@NonNull byte[] authToken) {
+ public int addAuthToken(@NonNull byte[] authToken) {
return addAuthToken(AuthTokenUtils.toHardwareAuthToken(authToken));
}
@@ -82,7 +88,7 @@
* is LSKF (or equivalent) and thus has made the synthetic password available
* @return 0 if successful or a {@code ResponseCode}.
*/
- public static int onDeviceUnlocked(int userId, @Nullable byte[] password) {
+ public int onDeviceUnlocked(int userId, @Nullable byte[] password) {
StrictMode.noteDiskWrite();
try {
getService().onDeviceUnlocked(userId, password);
@@ -103,7 +109,7 @@
* @param weakUnlockEnabled - true if non-strong biometric or trust agent unlock is enabled
* @return 0 if successful or a {@code ResponseCode}.
*/
- public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
+ public int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
boolean weakUnlockEnabled) {
StrictMode.noteDiskWrite();
try {
@@ -125,14 +131,17 @@
* @return the last authentication time or
* {@link BiometricConstants#BIOMETRIC_NO_AUTHENTICATION}.
*/
- public static long getLastAuthenticationTime(
- long userId, @HardwareAuthenticatorType int[] authenticatorTypes) {
+ public long getLastAuthTime(long userId, @HardwareAuthenticatorType int[] authenticatorTypes) {
try {
return getService().getLastAuthTime(userId, authenticatorTypes);
} catch (RemoteException | NullPointerException e) {
- Log.w(TAG, "Can not connect to keystore", e);
+ Log.w(TAG, "Error getting last auth time: " + e);
return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
} catch (ServiceSpecificException e) {
+ // This is returned when the feature flag test fails in keystore2
+ if (e.errorCode == ResponseCode.PERMISSION_DENIED) {
+ throw new UnsupportedOperationException();
+ }
return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
}
}
diff --git a/libs/WindowManager/Shell/aconfig/Android.bp b/libs/WindowManager/Shell/aconfig/Android.bp
index 1a98ffc..7f8f57b 100644
--- a/libs/WindowManager/Shell/aconfig/Android.bp
+++ b/libs/WindowManager/Shell/aconfig/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "com_android_wm_shell_flags",
package: "com.android.wm.shell",
+ container: "system",
srcs: [
"multitasking.aconfig",
],
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index b61dda4..7ff204c 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -1,4 +1,5 @@
package: "com.android.wm.shell"
+container: "system"
flag {
name: "enable_app_pairs"
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index a541c59..c2ba064 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -45,9 +45,6 @@
<!-- Allow PIP to resize to a slightly bigger state upon touch/showing the menu -->
<bool name="config_pipEnableResizeForMenu">true</bool>
- <!-- PiP minimum size, which is a % based off the shorter side of display width and height -->
- <fraction name="config_pipShortestEdgePercent">40%</fraction>
-
<!-- Time (duration in milliseconds) that the shell waits for an app to close the PiP by itself
if a custom action is present before closing it. -->
<integer name="config_pipForceCloseDelay">1000</integer>
@@ -91,11 +88,45 @@
16x16
</string>
+ <!-- Default percentages for the PIP size logic.
+ 1. Determine max widths
+ Subtract width of system UI and default padding from the shortest edge of the device.
+ This is the max width.
+ 2. Calculate Default and Mins
+ Default is config_pipSystemPreferredDefaultSizePercent of max-width/height.
+ Min is config_pipSystemPreferredMinimumSizePercent of it. -->
+ <item name="config_pipSystemPreferredDefaultSizePercent" format="float" type="dimen">0.6</item>
+ <item name="config_pipSystemPreferredMinimumSizePercent" format="float" type="dimen">0.5</item>
+ <!-- Default percentages for the PIP size logic when the Display is close to square.
+ This is used instead when the display is square-ish, like fold-ables when unfolded,
+ to make sure that default PiP does not cover the hinge (halfway of the display).
+ 0. Determine if the display is square-ish
+ If min(displayWidth, displayHeight) / max(displayWidth, displayHeight) is greater than
+ config_pipSquareDisplayThresholdForSystemPreferredSize, we use the percent for
+ square display listed below.
+ 1. Determine max widths
+ Subtract width of system UI and default padding from the shortest edge of the device.
+ This is the max width.
+ 2. Calculate Default and Mins
+ Default is config_pipSystemPreferredDefaultSizePercentForSquareDisplay of max-width/height.
+ Min is config_pipSystemPreferredMinimumSizePercentForSquareDisplay of it. -->
+ <item name="config_pipSquareDisplayThresholdForSystemPreferredSize"
+ format="float" type="dimen">0.95</item>
+ <item name="config_pipSystemPreferredDefaultSizePercentForSquareDisplay"
+ format="float" type="dimen">0.5</item>
+ <item name="config_pipSystemPreferredMinimumSizePercentForSquareDisplay"
+ format="float" type="dimen">0.4</item>
+
<!-- The percentage of the screen width to use for the default width or height of
picture-in-picture windows. Regardless of the percent set here, calculated size will never
- be smaller than @dimen/default_minimal_size_pip_resizable_task. -->
+ be smaller than @dimen/default_minimal_size_pip_resizable_task.
+ This is used in legacy spec, use config_pipSystemPreferredDefaultSizePercent instead. -->
<item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.23</item>
+ <!-- PiP minimum size, which is a % based off the shorter side of display width and height.
+ This is used in legacy spec, use config_pipSystemPreferredMinimumSizePercent instead. -->
+ <fraction name="config_pipShortestEdgePercent">40%</fraction>
+
<!-- The default aspect ratio for picture-in-picture windows. -->
<item name="config_pictureInPictureDefaultAspectRatio" format="float" type="dimen">
1.777778
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 0867a44..d295877 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -455,8 +455,7 @@
ProtoLog.d(WM_SHELL_BUBBLES,
"onActivityRestartAttempt - taskId=%d selecting matching bubble=%s",
task.taskId, b.getKey());
- mBubbleData.setSelectedBubble(b);
- mBubbleData.setExpanded(true);
+ mBubbleData.setSelectedBubbleAndExpandStack(b);
return;
}
}
@@ -593,13 +592,6 @@
}
}
- private void openBubbleOverflow() {
- ensureBubbleViewsAndWindowCreated();
- mBubbleData.setShowingOverflow(true);
- mBubbleData.setSelectedBubble(mBubbleData.getOverflow());
- mBubbleData.setExpanded(true);
- }
-
/**
* Called when the status bar has become visible or invisible (either permanently or
* temporarily).
@@ -1247,8 +1239,7 @@
}
if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) {
// already in the stack
- mBubbleData.setSelectedBubble(b);
- mBubbleData.setExpanded(true);
+ mBubbleData.setSelectedBubbleAndExpandStack(b);
} else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
// promote it out of the overflow
promoteBubbleFromOverflow(b);
@@ -1273,8 +1264,7 @@
String key = entry.getKey();
Bubble bubble = mBubbleData.getBubbleInStackWithKey(key);
if (bubble != null) {
- mBubbleData.setSelectedBubble(bubble);
- mBubbleData.setExpanded(true);
+ mBubbleData.setSelectedBubbleAndExpandStack(bubble);
} else {
bubble = mBubbleData.getOverflowBubbleWithKey(key);
if (bubble != null) {
@@ -1367,8 +1357,7 @@
} else {
// App bubble is not selected, select it & expand
Log.i(TAG, " showOrHideAppBubble, expand and select existing app bubble");
- mBubbleData.setSelectedBubble(existingAppBubble);
- mBubbleData.setExpanded(true);
+ mBubbleData.setSelectedBubbleAndExpandStack(existingAppBubble);
}
} else {
// Check if it exists in the overflow
@@ -2335,6 +2324,11 @@
mMainExecutor.execute(() ->
mController.setBubbleBarLocation(location));
}
+
+ @Override
+ public void setBubbleBarBounds(Rect bubbleBarBounds) {
+ mMainExecutor.execute(() -> mBubblePositioner.setBubbleBarBounds(bubbleBarBounds));
+ }
}
private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 61f0ed2..ae3d0c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -365,6 +365,19 @@
mSelectedBubble = bubble;
}
+ /**
+ * Sets the selected bubble and expands it.
+ *
+ * <p>This dispatches a single state update for both changes and should be used instead of
+ * calling {@link #setSelectedBubble(BubbleViewProvider)} followed by
+ * {@link #setExpanded(boolean)} immediately after, which will generate 2 separate updates.
+ */
+ public void setSelectedBubbleAndExpandStack(BubbleViewProvider bubble) {
+ setSelectedBubbleInternal(bubble);
+ setExpandedInternal(true);
+ dispatchPendingChanges();
+ }
+
public void setSelectedBubble(BubbleViewProvider bubble) {
setSelectedBubbleInternal(bubble);
dispatchPendingChanges();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 16134d3..c9f0f0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -44,4 +44,6 @@
oneway void showUserEducation(in int positionX, in int positionY) = 8;
oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9;
+
+ oneway void setBubbleBarBounds(in Rect bubbleBarBounds) = 10;
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 8af4c75..45ad631 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -166,13 +166,8 @@
bbev.setTaskViewAlpha(0f);
bbev.setVisibility(VISIBLE);
- // Set the pivot point for the scale, so the view animates out from the bubble bar.
- Rect bubbleBarBounds = mPositioner.getBubbleBarBounds();
- mExpandedViewContainerMatrix.setScale(
- 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT,
- bubbleBarBounds.centerX(),
- bubbleBarBounds.top);
+ setScaleFromBubbleBar(mExpandedViewContainerMatrix,
+ 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT);
bbev.setAnimationMatrix(mExpandedViewContainerMatrix);
@@ -214,8 +209,8 @@
}
bbev.setScaleX(1f);
bbev.setScaleY(1f);
- mExpandedViewContainerMatrix.setScaleX(1f);
- mExpandedViewContainerMatrix.setScaleY(1f);
+
+ setScaleFromBubbleBar(mExpandedViewContainerMatrix, 1f);
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
@@ -240,6 +235,16 @@
mExpandedViewAlphaAnimator.reverse();
}
+ private void setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale) {
+ // Set the pivot point for the scale, so the view animates out from the bubble bar.
+ Rect bubbleBarBounds = mPositioner.getBubbleBarBounds();
+ matrix.setScale(
+ scale,
+ scale,
+ bubbleBarBounds.centerX(),
+ bubbleBarBounds.top);
+ }
+
/**
* Animate the expanded bubble when it is being dragged
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
index 18c7bdd..7eb0f26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.content.res.Resources
-import android.os.SystemProperties
import android.util.Size
import com.android.wm.shell.R
import java.io.PrintWriter
@@ -36,30 +35,81 @@
private var mOverrideMinSize: Size? = null
- /** Default and minimum percentages for the PIP size logic. */
- private val mDefaultSizePercent: Float
- private val mMinimumSizePercent: Float
+ /**
+ * Default percentages for the PIP size logic.
+ * 1. Determine max widths
+ * Subtract width of system UI and default padding from the shortest edge of the device.
+ * This is the max width.
+ * 2. Calculate Default and Mins
+ * Default is mSystemPreferredDefaultSizePercent of max-width/height.
+ * Min is mSystemPreferredMinimumSizePercent of it.
+ *
+ * NOTE: Do not use this directly, use the mPreferredDefaultSizePercent getter instead.
+ */
+ private var mSystemPreferredDefaultSizePercent = 0.6f
+ /** Minimum percentages for the PIP size logic. */
+ private var mSystemPreferredMinimumSizePercent = 0.5f
+
+ /** Threshold to categorize the Display as square, calculated as min(w, h) / max(w, h). */
+ private var mSquareDisplayThresholdForSystemPreferredSize = 0.95f
+ /**
+ * Default percentages for the PIP size logic when the Display is square-ish.
+ * This is used instead when the display is square-ish, like fold-ables when unfolded,
+ * to make sure that default PiP does not cover the hinge (halfway of the display).
+ * 1. Determine max widths
+ * Subtract width of system UI and default padding from the shortest edge of the device.
+ * This is the max width.
+ * 2. Calculate Default and Mins
+ * Default is mSystemPreferredDefaultSizePercent of max-width/height.
+ * Min is mSystemPreferredMinimumSizePercent of it.
+ *
+ * NOTE: Do not use this directly, use the mPreferredDefaultSizePercent getter instead.
+ */
+ private var mSystemPreferredDefaultSizePercentForSquareDisplay = 0.5f
+ /** Minimum percentages for the PIP size logic. */
+ private var mSystemPreferredMinimumSizePercentForSquareDisplay = 0.4f
+
+ private val mIsSquareDisplay
+ get() = minOf(pipDisplayLayoutState.displayLayout.width(),
+ pipDisplayLayoutState.displayLayout.height()).toFloat() /
+ maxOf(pipDisplayLayoutState.displayLayout.width(),
+ pipDisplayLayoutState.displayLayout.height()) >
+ mSquareDisplayThresholdForSystemPreferredSize
+ private val mPreferredDefaultSizePercent
+ get() = if (mIsSquareDisplay) mSystemPreferredDefaultSizePercentForSquareDisplay else
+ mSystemPreferredDefaultSizePercent
+
+ private val mPreferredMinimumSizePercent
+ get() = if (mIsSquareDisplay) mSystemPreferredMinimumSizePercentForSquareDisplay else
+ mSystemPreferredMinimumSizePercent
/** Aspect ratio that the PIP size spec logic optimizes for. */
private var mOptimizedAspectRatio = 0f
init {
- mDefaultSizePercent = SystemProperties
- .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat()
- mMinimumSizePercent = SystemProperties
- .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat()
-
reloadResources()
}
private fun reloadResources() {
- val res: Resources = context.getResources()
+ val res: Resources = context.resources
mDefaultMinSize = res.getDimensionPixelSize(
R.dimen.default_minimal_size_pip_resizable_task)
mOverridableMinSize = res.getDimensionPixelSize(
R.dimen.overridable_minimal_size_pip_resizable_task)
+ mSystemPreferredDefaultSizePercent = res.getFloat(
+ R.dimen.config_pipSystemPreferredDefaultSizePercent)
+ mSystemPreferredMinimumSizePercent = res.getFloat(
+ R.dimen.config_pipSystemPreferredMinimumSizePercent)
+
+ mSquareDisplayThresholdForSystemPreferredSize = res.getFloat(
+ R.dimen.config_pipSquareDisplayThresholdForSystemPreferredSize)
+ mSystemPreferredDefaultSizePercentForSquareDisplay = res.getFloat(
+ R.dimen.config_pipSystemPreferredDefaultSizePercentForSquareDisplay)
+ mSystemPreferredMinimumSizePercentForSquareDisplay = res.getFloat(
+ R.dimen.config_pipSystemPreferredMinimumSizePercentForSquareDisplay)
+
val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio)
// make sure the optimized aspect ratio is valid with a default value to fall back to
mOptimizedAspectRatio = if (requestedOptAspRatio > 1) {
@@ -128,7 +178,7 @@
return minSize
}
val maxSize = getMaxSize(aspectRatio)
- val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent),
+ val defaultWidth = Math.max(Math.round(maxSize.width * mPreferredDefaultSizePercent),
minSize.width)
val defaultHeight = Math.round(defaultWidth / aspectRatio)
return Size(defaultWidth, defaultHeight)
@@ -146,8 +196,8 @@
return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
}
val maxSize = getMaxSize(aspectRatio)
- var minWidth = Math.round(maxSize.width * mMinimumSizePercent)
- var minHeight = Math.round(maxSize.height * mMinimumSizePercent)
+ var minWidth = Math.round(maxSize.width * mPreferredMinimumSizePercent)
+ var minHeight = Math.round(maxSize.height * mPreferredMinimumSizePercent)
// make sure the calculated min size is not smaller than the allowed default min size
if (aspectRatio > 1f) {
@@ -244,8 +294,8 @@
pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize)
pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize)
pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize)
- pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent)
- pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent)
+ pw.println(innerPrefix + "mDefaultSizePercent=" + mPreferredDefaultSizePercent)
+ pw.println(innerPrefix + "mMinimumSizePercent=" + mPreferredMinimumSizePercent)
pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio)
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index b86e39f..4eff3f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -23,19 +23,25 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
+import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
+import com.android.wm.shell.pip2.phone.PipTouchHandler;
import com.android.wm.shell.pip2.phone.PipTransition;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
@@ -62,6 +68,7 @@
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
+ PipTouchHandler pipTouchHandler,
@NonNull PipScheduler pipScheduler) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
@@ -109,4 +116,34 @@
return new PhonePipMenuController(context, pipBoundsState, pipMediaController,
systemWindows, pipUiEventLogger, mainExecutor, mainHandler);
}
+
+
+ @WMSingleton
+ @Provides
+ static PipTouchHandler providePipTouchHandler(Context context,
+ ShellInit shellInit,
+ PhonePipMenuController menuPhoneController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ @NonNull PipBoundsState pipBoundsState,
+ @NonNull SizeSpecSource sizeSpecSource,
+ PipMotionHelper pipMotionHelper,
+ FloatingContentCoordinator floatingContentCoordinator,
+ PipUiEventLogger pipUiEventLogger,
+ @ShellMainThread ShellExecutor mainExecutor,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
+ pipBoundsState, sizeSpecSource, pipMotionHelper, floatingContentCoordinator,
+ pipUiEventLogger, mainExecutor, pipPerfHintControllerOptional);
+ }
+
+ @WMSingleton
+ @Provides
+ static PipMotionHelper providePipMotionHelper(Context context,
+ PipBoundsState pipBoundsState, PhonePipMenuController menuController,
+ PipSnapAlgorithm pipSnapAlgorithm,
+ FloatingContentCoordinator floatingContentCoordinator,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm,
+ floatingContentCoordinator, pipPerfHintControllerOptional);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
new file mode 100644
index 0000000..e7e7970
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.DismissViewUtils;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
+import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+
+import kotlin.Unit;
+
+/**
+ * Handler of all Magnetized Object related code for PiP.
+ */
+public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListener {
+
+ /* The multiplier to apply scale the target size by when applying the magnetic field radius */
+ private static final float MAGNETIC_FIELD_RADIUS_MULTIPLIER = 1.25f;
+
+ /**
+ * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
+ * PIP.
+ */
+ private MagnetizedObject<Rect> mMagnetizedPip;
+
+ /**
+ * Container for the dismiss circle, so that it can be animated within the container via
+ * translation rather than within the WindowManager via slow layout animations.
+ */
+ private DismissView mTargetViewContainer;
+
+ /** Circle view used to render the dismiss target. */
+ private DismissCircleView mTargetView;
+
+ /**
+ * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
+ */
+ private MagnetizedObject.MagneticTarget mMagneticTarget;
+
+ // Allow dragging the PIP to a location to close it
+ private boolean mEnableDismissDragToEdge;
+
+ private int mTargetSize;
+ private int mDismissAreaHeight;
+ private float mMagneticFieldRadiusPercent = 1f;
+ private WindowInsets mWindowInsets;
+
+ private SurfaceControl mTaskLeash;
+ private boolean mHasDismissTargetSurface;
+
+ private final Context mContext;
+ private final PipMotionHelper mMotionHelper;
+ private final PipUiEventLogger mPipUiEventLogger;
+ private final WindowManager mWindowManager;
+ private final ShellExecutor mMainExecutor;
+
+ public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger,
+ PipMotionHelper motionHelper, ShellExecutor mainExecutor) {
+ mContext = context;
+ mPipUiEventLogger = pipUiEventLogger;
+ mMotionHelper = motionHelper;
+ mMainExecutor = mainExecutor;
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ void init() {
+ Resources res = mContext.getResources();
+ mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
+ mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+
+ if (mTargetViewContainer != null) {
+ // init can be called multiple times, remove the old one from view hierarchy first.
+ cleanUpDismissTarget();
+ }
+
+ mTargetViewContainer = new DismissView(mContext);
+ DismissViewUtils.setup(mTargetViewContainer);
+ mTargetView = mTargetViewContainer.getCircle();
+ mTargetViewContainer.setOnApplyWindowInsetsListener((view, windowInsets) -> {
+ if (!windowInsets.equals(mWindowInsets)) {
+ mWindowInsets = windowInsets;
+ updateMagneticTargetSize();
+ }
+ return windowInsets;
+ });
+
+ mMagnetizedPip = mMotionHelper.getMagnetizedPip();
+ mMagnetizedPip.clearAllTargets();
+ mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
+ updateMagneticTargetSize();
+
+ mMagnetizedPip.setAnimateStuckToTarget(
+ (target, velX, velY, flung, after) -> {
+ if (mEnableDismissDragToEdge) {
+ mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
+ }
+ return Unit.INSTANCE;
+ });
+ mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
+ // Show the dismiss target, in case the initial touch event occurred within
+ // the magnetic field radius.
+ if (mEnableDismissDragToEdge) {
+ showDismissTargetMaybe();
+ }
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject,
+ float velX, float velY, boolean wasFlungOut) {
+ if (wasFlungOut) {
+ mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
+ hideDismissTargetMaybe();
+ } else {
+ mMotionHelper.setSpringingToTouch(true);
+ }
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ @NonNull MagnetizedObject<?> draggedObject) {
+ if (mEnableDismissDragToEdge) {
+ mMainExecutor.executeDelayed(() -> {
+ mMotionHelper.notifyDismissalPending();
+ mMotionHelper.animateDismiss();
+ hideDismissTargetMaybe();
+
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
+ }, 0);
+ }
+ }
+ });
+
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
+ mHasDismissTargetSurface = true;
+ updateDismissTargetLayer();
+ return true;
+ }
+
+ /**
+ * Potentially start consuming future motion events if PiP is currently near the magnetized
+ * object.
+ */
+ public boolean maybeConsumeMotionEvent(MotionEvent ev) {
+ return mMagnetizedPip.maybeConsumeMotionEvent(ev);
+ }
+
+ /**
+ * Update the magnet size.
+ */
+ public void updateMagneticTargetSize() {
+ if (mTargetView == null) {
+ return;
+ }
+ if (mTargetViewContainer != null) {
+ mTargetViewContainer.updateResources();
+ }
+
+ final Resources res = mContext.getResources();
+ mTargetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
+ mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+
+ // Set the magnetic field radius equal to the target size from the center of the target
+ setMagneticFieldRadiusPercent(mMagneticFieldRadiusPercent);
+ }
+
+ /**
+ * Increase or decrease the field radius of the magnet object, e.g. with larger percent,
+ * PiP will magnetize to the field sooner.
+ */
+ public void setMagneticFieldRadiusPercent(float percent) {
+ mMagneticFieldRadiusPercent = percent;
+ mMagneticTarget.setMagneticFieldRadiusPx((int) (mMagneticFieldRadiusPercent * mTargetSize
+ * MAGNETIC_FIELD_RADIUS_MULTIPLIER));
+ }
+
+ public void setTaskLeash(SurfaceControl taskLeash) {
+ mTaskLeash = taskLeash;
+ }
+
+ private void updateDismissTargetLayer() {
+ if (!mHasDismissTargetSurface || mTaskLeash == null) {
+ // No dismiss target surface, can just return
+ return;
+ }
+
+ final SurfaceControl targetViewLeash =
+ mTargetViewContainer.getViewRootImpl().getSurfaceControl();
+ if (!targetViewLeash.isValid()) {
+ // The surface of mTargetViewContainer is somehow not ready, bail early
+ return;
+ }
+
+ // Put the dismiss target behind the task
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setRelativeLayer(targetViewLeash, mTaskLeash, -1);
+ t.apply();
+ }
+
+ /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
+ public void createOrUpdateDismissTarget() {
+ if (mTargetViewContainer.getParent() == null) {
+ mTargetViewContainer.cancelAnimators();
+
+ mTargetViewContainer.setVisibility(View.INVISIBLE);
+ mTargetViewContainer.getViewTreeObserver().removeOnPreDrawListener(this);
+ mHasDismissTargetSurface = false;
+
+ mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
+ } else {
+ mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams());
+ }
+ }
+
+ /** Returns layout params for the dismiss target, using the latest display metrics. */
+ private WindowManager.LayoutParams getDismissTargetLayoutParams() {
+ final Point windowSize = new Point();
+ mWindowManager.getDefaultDisplay().getRealSize(windowSize);
+ int height = Math.min(windowSize.y, mDismissAreaHeight);
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ height,
+ 0, windowSize.y - height,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+
+ lp.setTitle("pip-dismiss-overlay");
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ lp.setFitInsetsTypes(0 /* types */);
+
+ return lp;
+ }
+
+ /** Makes the dismiss target visible and animates it in, if it isn't already visible. */
+ public void showDismissTargetMaybe() {
+ if (!mEnableDismissDragToEdge) {
+ return;
+ }
+
+ createOrUpdateDismissTarget();
+
+ if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+ mTargetViewContainer.getViewTreeObserver().addOnPreDrawListener(this);
+ }
+ // always invoke show, since the target might still be VISIBLE while playing hide animation,
+ // so we want to ensure it will show back again
+ mTargetViewContainer.show();
+ }
+
+ /** Animates the magnetic dismiss target out and then sets it to GONE. */
+ public void hideDismissTargetMaybe() {
+ if (!mEnableDismissDragToEdge) {
+ return;
+ }
+ mTargetViewContainer.hide();
+ }
+
+ /**
+ * Removes the dismiss target and cancels any pending callbacks to show it.
+ */
+ public void cleanUpDismissTarget() {
+ if (mTargetViewContainer.getParent() != null) {
+ mWindowManager.removeViewImmediate(mTargetViewContainer);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
new file mode 100644
index 0000000..619bed4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
+import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
+
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_DISMISS;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Debug;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.FloatProperties;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.animation.PhysicsAnimator;
+
+import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * A helper to animate and manipulate the PiP.
+ */
+public class PipMotionHelper implements PipAppOpsListener.Callback,
+ FloatingContentCoordinator.FloatingContent {
+ private static final String TAG = "PipMotionHelper";
+ private static final boolean DEBUG = false;
+
+ private static final int SHRINK_STACK_FROM_MENU_DURATION = 250;
+ private static final int EXPAND_STACK_TO_MENU_DURATION = 250;
+ private static final int UNSTASH_DURATION = 250;
+ private static final int LEAVE_PIP_DURATION = 300;
+ private static final int SHIFT_DURATION = 300;
+
+ /** Friction to use for PIP when it moves via physics fling animations. */
+ private static final float DEFAULT_FRICTION = 1.9f;
+ /** How much of the dismiss circle size to use when scaling down PIP. **/
+ private static final float DISMISS_CIRCLE_PERCENT = 0.85f;
+
+ private final Context mContext;
+ private @NonNull PipBoundsState mPipBoundsState;
+
+ private PhonePipMenuController mMenuController;
+ private PipSnapAlgorithm mSnapAlgorithm;
+
+ /** The region that all of PIP must stay within. */
+ private final Rect mFloatingAllowedArea = new Rect();
+
+ /** Coordinator instance for resolving conflicts with other floating content. */
+ private FloatingContentCoordinator mFloatingContentCoordinator;
+
+ @Nullable private final PipPerfHintController mPipPerfHintController;
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ /**
+ * PhysicsAnimator instance for animating {@link PipBoundsState#getMotionBoundsState()}
+ * using physics animations.
+ */
+ private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator;
+
+ private MagnetizedObject<Rect> mMagnetizedPip;
+
+ /**
+ * Update listener that resizes the PIP to {@link PipBoundsState#getMotionBoundsState()}.
+ */
+ private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener;
+
+ /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
+ private PhysicsAnimator.FlingConfig mFlingConfigX;
+ private PhysicsAnimator.FlingConfig mFlingConfigY;
+ /** FlingConfig instances provided to PhysicsAnimator for stashing. */
+ private PhysicsAnimator.FlingConfig mStashConfigX;
+
+ /** SpringConfig to use for fling-then-spring animations. */
+ private final PhysicsAnimator.SpringConfig mSpringConfig =
+ new PhysicsAnimator.SpringConfig(700f, DAMPING_RATIO_NO_BOUNCY);
+
+ /** SpringConfig used for animating into the dismiss region, matches the one in
+ * {@link MagnetizedObject}. */
+ private final PhysicsAnimator.SpringConfig mAnimateToDismissSpringConfig =
+ new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_NO_BOUNCY);
+
+ /** SpringConfig used for animating the pip to catch up to the finger once it leaves the dismiss
+ * drag region. */
+ private final PhysicsAnimator.SpringConfig mCatchUpSpringConfig =
+ new PhysicsAnimator.SpringConfig(5000f, DAMPING_RATIO_NO_BOUNCY);
+
+ /** SpringConfig to use for springing PIP away from conflicting floating content. */
+ private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig =
+ new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_NO_BOUNCY);
+
+ private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
+ if (mPipBoundsState.getBounds().equals(newBounds)) {
+ return;
+ }
+
+ mMenuController.updateMenuLayout(newBounds);
+ mPipBoundsState.setBounds(newBounds);
+ };
+
+ /**
+ * Whether we're springing to the touch event location (vs. moving it to that position
+ * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
+ * 'stuck' in the target and needs to catch up to the touch location.
+ */
+ private boolean mSpringingToTouch = false;
+
+ /**
+ * Whether PIP was released in the dismiss target, and will be animated out and dismissed
+ * shortly.
+ */
+ private boolean mDismissalPending = false;
+
+ /**
+ * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is
+ * used to show menu activity when the expand animation is completed.
+ */
+ private Runnable mPostPipTransitionCallback;
+
+ public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
+ PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm,
+ FloatingContentCoordinator floatingContentCoordinator,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ mContext = context;
+ mPipBoundsState = pipBoundsState;
+ mMenuController = menuController;
+ mSnapAlgorithm = snapAlgorithm;
+ mFloatingContentCoordinator = floatingContentCoordinator;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+ mResizePipUpdateListener = (target, values) -> {
+ if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
+ /*
+ mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion(), null);
+ */
+ }
+ };
+ }
+
+ void init() {
+ mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+ }
+
+ @NonNull
+ @Override
+ public Rect getFloatingBoundsOnScreen() {
+ return !mPipBoundsState.getMotionBoundsState().getAnimatingToBounds().isEmpty()
+ ? mPipBoundsState.getMotionBoundsState().getAnimatingToBounds() : getBounds();
+ }
+
+ @NonNull
+ @Override
+ public Rect getAllowedFloatingBoundsRegion() {
+ return mFloatingAllowedArea;
+ }
+
+ @Override
+ public void moveToBounds(@NonNull Rect bounds) {
+ animateToBounds(bounds, mConflictResolutionSpringConfig);
+ }
+
+ /**
+ * Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations.
+ */
+ void synchronizePinnedStackBounds() {
+ cancelPhysicsAnimation();
+ mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
+
+ /*
+ if (mPipTaskOrganizer.isInPip()) {
+ mFloatingContentCoordinator.onContentMoved(this);
+ }
+ */
+ }
+
+ /**
+ * Tries to move the pinned stack to the given {@param bounds}.
+ */
+ void movePip(Rect toBounds) {
+ movePip(toBounds, false /* isDragging */);
+ }
+
+ /**
+ * Tries to move the pinned stack to the given {@param bounds}.
+ *
+ * @param isDragging Whether this movement is the result of a drag touch gesture. If so, we
+ * won't notify the floating content coordinator of this move, since that will
+ * happen when the gesture ends.
+ */
+ void movePip(Rect toBounds, boolean isDragging) {
+ if (!isDragging) {
+ mFloatingContentCoordinator.onContentMoved(this);
+ }
+
+ if (!mSpringingToTouch) {
+ // If we are moving PIP directly to the touch event locations, cancel any animations and
+ // move PIP to the given bounds.
+ cancelPhysicsAnimation();
+
+ if (!isDragging) {
+ resizePipUnchecked(toBounds);
+ mPipBoundsState.setBounds(toBounds);
+ } else {
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds);
+ /*
+ mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
+ (Rect newBounds) -> {
+ mMenuController.updateMenuLayout(newBounds);
+ });
+ */
+ }
+ } else {
+ // If PIP is 'catching up' after being stuck in the dismiss target, update the animation
+ // to spring towards the new touch location.
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mCatchUpSpringConfig)
+ .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mCatchUpSpringConfig)
+ .spring(FloatProperties.RECT_X, toBounds.left, mCatchUpSpringConfig)
+ .spring(FloatProperties.RECT_Y, toBounds.top, mCatchUpSpringConfig);
+
+ startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */);
+ }
+ }
+
+ /** Animates the PIP into the dismiss target, scaling it down. */
+ void animateIntoDismissTarget(
+ MagnetizedObject.MagneticTarget target,
+ float velX, float velY,
+ boolean flung, Function0<Unit> after) {
+ final PointF targetCenter = target.getCenterOnScreen();
+
+ // PIP should fit in the circle
+ final float dismissCircleSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.dismiss_circle_size);
+
+ final float width = getBounds().width();
+ final float height = getBounds().height();
+ final float ratio = width / height;
+
+ // Width should be a little smaller than the circle size.
+ final float desiredWidth = dismissCircleSize * DISMISS_CIRCLE_PERCENT;
+ final float desiredHeight = desiredWidth / ratio;
+ final float destinationX = targetCenter.x - (desiredWidth / 2f);
+ final float destinationY = targetCenter.y - (desiredHeight / 2f);
+
+ // If we're already in the dismiss target area, then there won't be a move to set the
+ // temporary bounds, so just initialize it to the current bounds.
+ if (!mPipBoundsState.getMotionBoundsState().isInMotion()) {
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
+ }
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_X, destinationX, velX, mAnimateToDismissSpringConfig)
+ .spring(FloatProperties.RECT_Y, destinationY, velY, mAnimateToDismissSpringConfig)
+ .spring(FloatProperties.RECT_WIDTH, desiredWidth, mAnimateToDismissSpringConfig)
+ .spring(FloatProperties.RECT_HEIGHT, desiredHeight, mAnimateToDismissSpringConfig)
+ .withEndActions(after);
+
+ startBoundsAnimator(destinationX, destinationY);
+ }
+
+ /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
+ void setSpringingToTouch(boolean springingToTouch) {
+ mSpringingToTouch = springingToTouch;
+ }
+
+ /**
+ * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
+ * * fullscreen depending on the display area's windowing mode.
+ */
+ void expandLeavePip(boolean skipAnimation) {
+ expandLeavePip(skipAnimation, false /* enterSplit */);
+ }
+
+ /**
+ * Resizes the pinned task to split-screen mode.
+ */
+ void expandIntoSplit() {
+ expandLeavePip(false, true /* enterSplit */);
+ }
+
+ /**
+ * Resizes the pinned stack back to unknown windowing mode, which could be freeform or
+ * fullscreen depending on the display area's windowing mode.
+ */
+ private void expandLeavePip(boolean skipAnimation, boolean enterSplit) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: exitPip: skipAnimation=%s"
+ + " callers=\n%s", TAG, skipAnimation, Debug.getCallers(5, " "));
+ }
+ cancelPhysicsAnimation();
+ mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
+ // mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION, enterSplit);
+ }
+
+ /**
+ * Dismisses the pinned stack.
+ */
+ @Override
+ public void dismissPip() {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: removePip: callers=\n%s", TAG, Debug.getCallers(5, " "));
+ }
+ cancelPhysicsAnimation();
+ mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
+ // mPipTaskOrganizer.removePip();
+ }
+
+ /** Sets the movement bounds to use to constrain PIP position animations. */
+ void onMovementBoundsChanged() {
+ rebuildFlingConfigs();
+
+ // The movement bounds represent the area within which we can move PIP's top-left position.
+ // The allowed area for all of PIP is those bounds plus PIP's width and height.
+ mFloatingAllowedArea.set(mPipBoundsState.getMovementBounds());
+ mFloatingAllowedArea.right += getBounds().width();
+ mFloatingAllowedArea.bottom += getBounds().height();
+ }
+
+ /**
+ * @return the PiP bounds.
+ */
+ private Rect getBounds() {
+ return mPipBoundsState.getBounds();
+ }
+
+ /**
+ * Flings the PiP to the closest snap target.
+ */
+ void flingToSnapTarget(
+ float velocityX, float velocityY, @Nullable Runnable postBoundsUpdateCallback) {
+ movetoTarget(velocityX, velocityY, postBoundsUpdateCallback, false /* isStash */);
+ }
+
+ /**
+ * Stash PiP to the closest edge. We set velocityY to 0 to limit pure horizontal motion.
+ */
+ void stashToEdge(float velX, float velY, @Nullable Runnable postBoundsUpdateCallback) {
+ velY = mPipBoundsState.getStashedState() == STASH_TYPE_NONE ? 0 : velY;
+ movetoTarget(velX, velY, postBoundsUpdateCallback, true /* isStash */);
+ }
+
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ private void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
+ private void movetoTarget(
+ float velocityX,
+ float velocityY,
+ @Nullable Runnable postBoundsUpdateCallback,
+ boolean isStash) {
+ // If we're flinging to a snap target now, we're not springing to catch up to the touch
+ // location now.
+ mSpringingToTouch = false;
+
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
+ .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
+ .flingThenSpring(
+ FloatProperties.RECT_X, velocityX,
+ isStash ? mStashConfigX : mFlingConfigX,
+ mSpringConfig, true /* flingMustReachMinOrMax */)
+ .flingThenSpring(
+ FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig);
+
+ final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+ final float leftEdge = isStash
+ ? mPipBoundsState.getStashOffset() - mPipBoundsState.getBounds().width()
+ + insetBounds.left
+ : mPipBoundsState.getMovementBounds().left;
+ final float rightEdge = isStash
+ ? mPipBoundsState.getDisplayBounds().right - mPipBoundsState.getStashOffset()
+ - insetBounds.right
+ : mPipBoundsState.getMovementBounds().right;
+
+ final float xEndValue = velocityX < 0 ? leftEdge : rightEdge;
+
+ final int startValueY = mPipBoundsState.getMotionBoundsState().getBoundsInMotion().top;
+ final float estimatedFlingYEndValue =
+ PhysicsAnimator.estimateFlingEndValue(startValueY, velocityY, mFlingConfigY);
+
+ startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */,
+ postBoundsUpdateCallback);
+ }
+
+ /**
+ * Animates PIP to the provided bounds, using physics animations and the given spring
+ * configuration
+ */
+ void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) {
+ if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
+ // Animate from the current bounds if we're not already animating.
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(getBounds());
+ }
+
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_X, bounds.left, springConfig)
+ .spring(FloatProperties.RECT_Y, bounds.top, springConfig);
+ startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */);
+ }
+
+ /**
+ * Animates the dismissal of the PiP off the edge of the screen.
+ */
+ void animateDismiss() {
+ // Animate off the bottom of the screen, then dismiss PIP.
+ mTemporaryBoundsPhysicsAnimator
+ .spring(FloatProperties.RECT_Y,
+ mPipBoundsState.getMovementBounds().bottom + getBounds().height() * 2,
+ 0,
+ mSpringConfig)
+ .withEndActions(this::dismissPip);
+
+ startBoundsAnimator(
+ getBounds().left /* toX */, getBounds().bottom + getBounds().height() /* toY */);
+
+ mDismissalPending = false;
+ }
+
+ /**
+ * Animates the PiP to the expanded state to show the menu.
+ */
+ float animateToExpandedState(Rect expandedBounds, Rect movementBounds,
+ Rect expandedMovementBounds, Runnable callback) {
+ float savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(getBounds()),
+ movementBounds);
+ mSnapAlgorithm.applySnapFraction(expandedBounds, expandedMovementBounds, savedSnapFraction);
+ mPostPipTransitionCallback = callback;
+ resizeAndAnimatePipUnchecked(expandedBounds, EXPAND_STACK_TO_MENU_DURATION);
+ return savedSnapFraction;
+ }
+
+ /**
+ * Animates the PiP from the expanded state to the normal state after the menu is hidden.
+ */
+ void animateToUnexpandedState(Rect normalBounds, float savedSnapFraction,
+ Rect normalMovementBounds, Rect currentMovementBounds, boolean immediate) {
+ if (savedSnapFraction < 0f) {
+ // If there are no saved snap fractions, then just use the current bounds
+ savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(getBounds()),
+ currentMovementBounds, mPipBoundsState.getStashedState());
+ }
+
+ mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction,
+ mPipBoundsState.getStashedState(), mPipBoundsState.getStashOffset(),
+ mPipBoundsState.getDisplayBounds(),
+ mPipBoundsState.getDisplayLayout().stableInsets());
+
+ if (immediate) {
+ movePip(normalBounds);
+ } else {
+ resizeAndAnimatePipUnchecked(normalBounds, SHRINK_STACK_FROM_MENU_DURATION);
+ }
+ }
+
+ /**
+ * Animates the PiP to the stashed state, choosing the closest edge.
+ */
+ void animateToStashedClosestEdge() {
+ Rect tmpBounds = new Rect();
+ final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+ final int stashType =
+ mPipBoundsState.getBounds().left == mPipBoundsState.getMovementBounds().left
+ ? STASH_TYPE_LEFT : STASH_TYPE_RIGHT;
+ final float leftEdge = stashType == STASH_TYPE_LEFT
+ ? mPipBoundsState.getStashOffset()
+ - mPipBoundsState.getBounds().width() + insetBounds.left
+ : mPipBoundsState.getDisplayBounds().right
+ - mPipBoundsState.getStashOffset() - insetBounds.right;
+ tmpBounds.set((int) leftEdge,
+ mPipBoundsState.getBounds().top,
+ (int) (leftEdge + mPipBoundsState.getBounds().width()),
+ mPipBoundsState.getBounds().bottom);
+ resizeAndAnimatePipUnchecked(tmpBounds, UNSTASH_DURATION);
+ mPipBoundsState.setStashed(stashType);
+ }
+
+ /**
+ * Animates the PiP from stashed state into un-stashed, popping it out from the edge.
+ */
+ void animateToUnStashedBounds(Rect unstashedBounds) {
+ resizeAndAnimatePipUnchecked(unstashedBounds, UNSTASH_DURATION);
+ }
+
+ /**
+ * Animates the PiP to offset it from the IME or shelf.
+ */
+ void animateToOffset(Rect originalBounds, int offset) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: animateToOffset: originalBounds=%s offset=%s"
+ + " callers=\n%s", TAG, originalBounds, offset,
+ Debug.getCallers(5, " "));
+ }
+ cancelPhysicsAnimation();
+ /*
+ mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION,
+ mUpdateBoundsCallback);
+ */
+ }
+
+ /**
+ * Cancels all existing animations.
+ */
+ private void cancelPhysicsAnimation() {
+ mTemporaryBoundsPhysicsAnimator.cancel();
+ mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
+ mSpringingToTouch = false;
+ }
+
+ /** Set new fling configs whose min/max values respect the given movement bounds. */
+ private void rebuildFlingConfigs() {
+ mFlingConfigX = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
+ mPipBoundsState.getMovementBounds().left,
+ mPipBoundsState.getMovementBounds().right);
+ mFlingConfigY = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
+ mPipBoundsState.getMovementBounds().top,
+ mPipBoundsState.getMovementBounds().bottom);
+ final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
+ mStashConfigX = new PhysicsAnimator.FlingConfig(
+ DEFAULT_FRICTION,
+ mPipBoundsState.getStashOffset() - mPipBoundsState.getBounds().width()
+ + insetBounds.left,
+ mPipBoundsState.getDisplayBounds().right - mPipBoundsState.getStashOffset()
+ - insetBounds.right);
+ }
+
+ private void startBoundsAnimator(float toX, float toY) {
+ startBoundsAnimator(toX, toY, null /* postBoundsUpdateCallback */);
+ }
+
+ /**
+ * Starts the physics animator which will update the animated PIP bounds using physics
+ * animations, as well as the TimeAnimator which will apply those bounds to PIP.
+ *
+ * This will also add end actions to the bounds animator that cancel the TimeAnimator and update
+ * the 'real' bounds to equal the final animated bounds.
+ *
+ * If one wishes to supply a callback after all the 'real' bounds update has happened,
+ * pass @param postBoundsUpdateCallback.
+ */
+ private void startBoundsAnimator(float toX, float toY, Runnable postBoundsUpdateCallback) {
+ if (!mSpringingToTouch) {
+ cancelPhysicsAnimation();
+ }
+
+ setAnimatingToBounds(new Rect(
+ (int) toX,
+ (int) toY,
+ (int) toX + getBounds().width(),
+ (int) toY + getBounds().height()));
+
+ if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
+ if (mPipPerfHintController != null) {
+ // Start a high perf session with a timeout callback.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "startBoundsAnimator");
+ }
+ if (postBoundsUpdateCallback != null) {
+ mTemporaryBoundsPhysicsAnimator
+ .addUpdateListener(mResizePipUpdateListener)
+ .withEndActions(this::onBoundsPhysicsAnimationEnd,
+ postBoundsUpdateCallback);
+ } else {
+ mTemporaryBoundsPhysicsAnimator
+ .addUpdateListener(mResizePipUpdateListener)
+ .withEndActions(this::onBoundsPhysicsAnimationEnd);
+ }
+ }
+
+ mTemporaryBoundsPhysicsAnimator.start();
+ }
+
+ /**
+ * Notify that PIP was released in the dismiss target and will be animated out and dismissed
+ * shortly.
+ */
+ void notifyDismissalPending() {
+ mDismissalPending = true;
+ }
+
+ private void onBoundsPhysicsAnimationEnd() {
+ // The physics animation ended, though we may not necessarily be done animating, such as
+ // when we're still dragging after moving out of the magnetic target.
+ if (!mDismissalPending
+ && !mSpringingToTouch
+ && !mMagnetizedPip.getObjectStuckToTarget()) {
+ // All motion operations have actually finished.
+ mPipBoundsState.setBounds(
+ mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
+ mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded();
+ if (!mDismissalPending) {
+ // do not schedule resize if PiP is dismissing, which may cause app re-open to
+ // mBounds instead of its normal bounds.
+ // mPipTaskOrganizer.scheduleFinishResizePip(getBounds());
+ }
+ }
+ mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded();
+ mSpringingToTouch = false;
+ mDismissalPending = false;
+ cleanUpHighPerfSessionMaybe();
+ }
+
+ /**
+ * Notifies the floating coordinator that we're moving, and sets the animating to bounds so
+ * we return these bounds from
+ * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}.
+ */
+ private void setAnimatingToBounds(Rect bounds) {
+ mPipBoundsState.getMotionBoundsState().setAnimatingToBounds(bounds);
+ mFloatingContentCoordinator.onContentMoved(this);
+ }
+
+ /**
+ * Directly resizes the PiP to the given {@param bounds}.
+ */
+ private void resizePipUnchecked(Rect toBounds) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: resizePipUnchecked: toBounds=%s"
+ + " callers=\n%s", TAG, toBounds, Debug.getCallers(5, " "));
+ }
+ if (!toBounds.equals(getBounds())) {
+ // mPipTaskOrganizer.scheduleResizePip(toBounds, mUpdateBoundsCallback);
+ }
+ }
+
+ /**
+ * Directly resizes the PiP to the given {@param bounds}.
+ */
+ private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) {
+ if (DEBUG) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: resizeAndAnimatePipUnchecked: toBounds=%s"
+ + " duration=%s callers=\n%s", TAG, toBounds, duration,
+ Debug.getCallers(5, " "));
+ }
+
+ // Intentionally resize here even if the current bounds match the destination bounds.
+ // This is so all the proper callbacks are performed.
+
+ // mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration,
+ // TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND, null /* updateBoundsCallback */);
+ // setAnimatingToBounds(toBounds);
+ }
+
+ /**
+ * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the
+ * magnetic dismiss target so it can calculate PIP's size and position.
+ */
+ MagnetizedObject<Rect> getMagnetizedPip() {
+ if (mMagnetizedPip == null) {
+ mMagnetizedPip = new MagnetizedObject<Rect>(
+ mContext, mPipBoundsState.getMotionBoundsState().getBoundsInMotion(),
+ FloatProperties.RECT_X, FloatProperties.RECT_Y) {
+ @Override
+ public float getWidth(@NonNull Rect animatedPipBounds) {
+ return animatedPipBounds.width();
+ }
+
+ @Override
+ public float getHeight(@NonNull Rect animatedPipBounds) {
+ return animatedPipBounds.height();
+ }
+
+ @Override
+ public void getLocationOnScreen(
+ @NonNull Rect animatedPipBounds, @NonNull int[] loc) {
+ loc[0] = animatedPipBounds.left;
+ loc[1] = animatedPipBounds.top;
+ }
+ };
+ mMagnetizedPip.setFlingToTargetEnabled(false);
+ }
+
+ return mMagnetizedPip;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
new file mode 100644
index 0000000..cc8e3e0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -0,0 +1,1081 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_NONE;
+import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_RIGHT;
+import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.wm.shell.pip2.phone.PhonePipMenuController.MENU_STATE_FULL;
+import static com.android.wm.shell.pip2.phone.PhonePipMenuController.MENU_STATE_NONE;
+import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.provider.DeviceConfig;
+import android.util.Size;
+import android.view.DisplayCutout;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
+import com.android.wm.shell.common.pip.PipPerfHintController;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.pip.PipAnimationController;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.io.PrintWriter;
+import java.util.Optional;
+
+/**
+ * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
+ * the PIP.
+ */
+public class PipTouchHandler {
+
+ private static final String TAG = "PipTouchHandler";
+ private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
+
+ // Allow PIP to resize to a slightly bigger state upon touch
+ private boolean mEnableResize;
+ private final Context mContext;
+ private final PipBoundsAlgorithm mPipBoundsAlgorithm;
+ @NonNull private final PipBoundsState mPipBoundsState;
+ @NonNull private final SizeSpecSource mSizeSpecSource;
+ private final PipUiEventLogger mPipUiEventLogger;
+ private final PipDismissTargetHandler mPipDismissTargetHandler;
+ private final ShellExecutor mMainExecutor;
+ @Nullable private final PipPerfHintController mPipPerfHintController;
+
+ private PipResizeGestureHandler mPipResizeGestureHandler;
+
+ private final PhonePipMenuController mMenuController;
+ private final AccessibilityManager mAccessibilityManager;
+
+ /**
+ * Whether PIP stash is enabled or not. When enabled, if the user flings toward the edge of the
+ * screen, it will be shown in "stashed" mode, where PIP will only show partially.
+ */
+ private boolean mEnableStash = true;
+
+ private float mStashVelocityThreshold;
+
+ // The reference inset bounds, used to determine the dismiss fraction
+ private final Rect mInsetBounds = new Rect();
+
+ // Used to workaround an issue where the WM rotation happens before we are notified, allowing
+ // us to send stale bounds
+ private int mDeferResizeToNormalBoundsUntilRotation = -1;
+ private int mDisplayRotation;
+
+ // Behaviour states
+ private int mMenuState = MENU_STATE_NONE;
+ private boolean mIsImeShowing;
+ private int mImeHeight;
+ private int mImeOffset;
+ private boolean mIsShelfShowing;
+ private int mShelfHeight;
+ private int mMovementBoundsExtraOffsets;
+ private int mBottomOffsetBufferPx;
+ private float mSavedSnapFraction = -1f;
+ private boolean mSendingHoverAccessibilityEvents;
+ private boolean mMovementWithinDismiss;
+
+ // Touch state
+ private final PipTouchState mTouchState;
+ private final FloatingContentCoordinator mFloatingContentCoordinator;
+ private PipMotionHelper mMotionHelper;
+ private PipTouchGesture mGesture;
+
+ // Temp vars
+ private final Rect mTmpBounds = new Rect();
+
+ /**
+ * A listener for the PIP menu activity.
+ */
+ private class PipMenuListener implements PhonePipMenuController.Listener {
+ @Override
+ public void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+ PipTouchHandler.this.onPipMenuStateChangeStart(menuState, resize, callback);
+ }
+
+ @Override
+ public void onPipMenuStateChangeFinish(int menuState) {
+ setMenuState(menuState);
+ }
+
+ @Override
+ public void onPipExpand() {
+ mMotionHelper.expandLeavePip(false /* skipAnimation */);
+ }
+
+ @Override
+ public void onPipDismiss() {
+ mTouchState.removeDoubleTapTimeoutCallback();
+ mMotionHelper.dismissPip();
+ }
+
+ @Override
+ public void onPipShowMenu() {
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(), shouldShowResizeHandle());
+ }
+ }
+
+ @SuppressLint("InflateParams")
+ public PipTouchHandler(Context context,
+ ShellInit shellInit,
+ PhonePipMenuController menuController,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ @NonNull PipBoundsState pipBoundsState,
+ @NonNull SizeSpecSource sizeSpecSource,
+ PipMotionHelper pipMotionHelper,
+ FloatingContentCoordinator floatingContentCoordinator,
+ PipUiEventLogger pipUiEventLogger,
+ ShellExecutor mainExecutor,
+ Optional<PipPerfHintController> pipPerfHintControllerOptional) {
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+ mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mPipBoundsState = pipBoundsState;
+ mSizeSpecSource = sizeSpecSource;
+ mMenuController = menuController;
+ mPipUiEventLogger = pipUiEventLogger;
+ mFloatingContentCoordinator = floatingContentCoordinator;
+ mMenuController.addListener(new PipMenuListener());
+ mGesture = new DefaultPipTouchGesture();
+ mMotionHelper = pipMotionHelper;
+ mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
+ mMotionHelper, mainExecutor);
+ mTouchState = new PipTouchState(ViewConfiguration.get(context),
+ () -> {
+ if (mPipBoundsState.isStashed()) {
+ animateToUnStashedState();
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ } else {
+ mMenuController.showMenuWithPossibleDelay(MENU_STATE_FULL,
+ mPipBoundsState.getBounds(), true /* allowMenuTimeout */,
+ willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+ },
+ menuController::hideMenu,
+ mainExecutor);
+ mPipResizeGestureHandler =
+ new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
+ mTouchState, this::updateMovementBounds, pipUiEventLogger,
+ menuController, mainExecutor, mPipPerfHintController);
+
+ if (PipUtils.isPip2ExperimentEnabled()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ /**
+ * Called when the touch handler is initialized.
+ */
+ public void onInit() {
+ Resources res = mContext.getResources();
+ mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
+ reloadResources();
+
+ mMotionHelper.init();
+ mPipResizeGestureHandler.init();
+ mPipDismissTargetHandler.init();
+
+ mEnableStash = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ PIP_STASHING,
+ /* defaultValue = */ true);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mMainExecutor,
+ properties -> {
+ if (properties.getKeyset().contains(PIP_STASHING)) {
+ mEnableStash = properties.getBoolean(
+ PIP_STASHING, /* defaultValue = */ true);
+ }
+ });
+ mStashVelocityThreshold = DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+ DEFAULT_STASH_VELOCITY_THRESHOLD);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+ mMainExecutor,
+ properties -> {
+ if (properties.getKeyset().contains(PIP_STASH_MINIMUM_VELOCITY_THRESHOLD)) {
+ mStashVelocityThreshold = properties.getFloat(
+ PIP_STASH_MINIMUM_VELOCITY_THRESHOLD,
+ DEFAULT_STASH_VELOCITY_THRESHOLD);
+ }
+ });
+ }
+
+ public PipTransitionController getTransitionHandler() {
+ // return mPipTaskOrganizer.getTransitionController();
+ return null;
+ }
+
+ private void reloadResources() {
+ final Resources res = mContext.getResources();
+ mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
+ mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
+ mPipDismissTargetHandler.updateMagneticTargetSize();
+ }
+
+ void onOverlayChanged() {
+ // onOverlayChanged is triggered upon theme change, update the dismiss target accordingly.
+ mPipDismissTargetHandler.init();
+ }
+
+ private boolean shouldShowResizeHandle() {
+ return false;
+ }
+
+ void setTouchGesture(PipTouchGesture gesture) {
+ mGesture = gesture;
+ }
+
+ void setTouchEnabled(boolean enabled) {
+ mTouchState.setAllowTouches(enabled);
+ }
+
+ void showPictureInPictureMenu() {
+ // Only show the menu if the user isn't currently interacting with the PiP
+ if (!mTouchState.isUserInteracting()) {
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ false /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+ }
+
+ void onActivityPinned() {
+ mPipDismissTargetHandler.createOrUpdateDismissTarget();
+
+ mPipResizeGestureHandler.onActivityPinned();
+ mFloatingContentCoordinator.onContentAdded(mMotionHelper);
+ }
+
+ void onActivityUnpinned(ComponentName topPipActivity) {
+ if (topPipActivity == null) {
+ // Clean up state after the last PiP activity is removed
+ mPipDismissTargetHandler.cleanUpDismissTarget();
+
+ mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
+ }
+ mPipResizeGestureHandler.onActivityUnpinned();
+ }
+
+ void onPinnedStackAnimationEnded(
+ @PipAnimationController.TransitionDirection int direction) {
+ // Always synchronize the motion helper bounds once PiP animations finish
+ mMotionHelper.synchronizePinnedStackBounds();
+ updateMovementBounds();
+ if (direction == TRANSITION_DIRECTION_TO_PIP) {
+ // Set the initial bounds as the user resize bounds.
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+ }
+ }
+
+ void onConfigurationChanged() {
+ mPipResizeGestureHandler.onConfigurationChanged();
+ mMotionHelper.synchronizePinnedStackBounds();
+ reloadResources();
+
+ /*
+ if (mPipTaskOrganizer.isInPip()) {
+ // Recreate the dismiss target for the new orientation.
+ mPipDismissTargetHandler.createOrUpdateDismissTarget();
+ }
+ */
+ }
+
+ void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ mIsImeShowing = imeVisible;
+ mImeHeight = imeHeight;
+ }
+
+ void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+ mIsShelfShowing = shelfVisible;
+ mShelfHeight = shelfHeight;
+ }
+
+ /**
+ * Called when SysUI state changed.
+ *
+ * @param isSysUiStateValid Is SysUI valid or not.
+ */
+ public void onSystemUiStateChanged(boolean isSysUiStateValid) {
+ mPipResizeGestureHandler.onSystemUiStateChanged(isSysUiStateValid);
+ }
+
+ void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
+ final Rect toMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0);
+ final int prevBottom = mPipBoundsState.getMovementBounds().bottom
+ - mMovementBoundsExtraOffsets;
+ if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) {
+ outBounds.offsetTo(outBounds.left, toMovementBounds.bottom);
+ }
+ }
+
+ /**
+ * Responds to IPinnedStackListener on resetting aspect ratio for the pinned window.
+ */
+ public void onAspectRatioChanged() {
+ mPipResizeGestureHandler.invalidateUserResizeBounds();
+ }
+
+ void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
+ boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
+ // Set the user resized bounds equal to the new normal bounds in case they were
+ // invalidated (e.g. by an aspect ratio change).
+ if (mPipResizeGestureHandler.getUserResizeBounds().isEmpty()) {
+ mPipResizeGestureHandler.setUserResizeBounds(normalBounds);
+ }
+
+ final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
+ final boolean fromDisplayRotationChanged = (mDisplayRotation != displayRotation);
+ if (fromDisplayRotationChanged) {
+ mTouchState.reset();
+ }
+
+ // Re-calculate the expanded bounds
+ Rect normalMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(normalBounds, insetBounds,
+ normalMovementBounds, bottomOffset);
+
+ if (mPipBoundsState.getMovementBounds().isEmpty()) {
+ // mMovementBounds is not initialized yet and a clean movement bounds without
+ // bottom offset shall be used later in this function.
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, insetBounds,
+ mPipBoundsState.getMovementBounds(), 0 /* bottomOffset */);
+ }
+
+ // Calculate the expanded size
+ float aspectRatio = (float) normalBounds.width() / normalBounds.height();
+ Size expandedSize = mSizeSpecSource.getDefaultSize(aspectRatio);
+ mPipBoundsState.setExpandedBounds(
+ new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
+ Rect expandedMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(
+ mPipBoundsState.getExpandedBounds(), insetBounds, expandedMovementBounds,
+ bottomOffset);
+
+ updatePipSizeConstraints(normalBounds, aspectRatio);
+
+ // The extra offset does not really affect the movement bounds, but are applied based on the
+ // current state (ime showing, or shelf offset) when we need to actually shift
+ int extraOffset = Math.max(
+ mIsImeShowing ? mImeOffset : 0,
+ !mIsImeShowing && mIsShelfShowing ? mShelfHeight : 0);
+
+ // Update the movement bounds after doing the calculations based on the old movement bounds
+ // above
+ mPipBoundsState.setNormalMovementBounds(normalMovementBounds);
+ mPipBoundsState.setExpandedMovementBounds(expandedMovementBounds);
+ mDisplayRotation = displayRotation;
+ mInsetBounds.set(insetBounds);
+ updateMovementBounds();
+ mMovementBoundsExtraOffsets = extraOffset;
+
+ // If we have a deferred resize, apply it now
+ if (mDeferResizeToNormalBoundsUntilRotation == displayRotation) {
+ mMotionHelper.animateToUnexpandedState(normalBounds, mSavedSnapFraction,
+ mPipBoundsState.getNormalMovementBounds(), mPipBoundsState.getMovementBounds(),
+ true /* immediate */);
+ mSavedSnapFraction = -1f;
+ mDeferResizeToNormalBoundsUntilRotation = -1;
+ }
+ }
+
+ /**
+ * Update the values for min/max allowed size of picture in picture window based on the aspect
+ * ratio.
+ * @param aspectRatio aspect ratio to use for the calculation of min/max size
+ */
+ public void updateMinMaxSize(float aspectRatio) {
+ updatePipSizeConstraints(mPipBoundsState.getNormalBounds(),
+ aspectRatio);
+ }
+
+ private void updatePipSizeConstraints(Rect normalBounds,
+ float aspectRatio) {
+ if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+ updatePinchResizeSizeConstraints(aspectRatio);
+ } else {
+ mPipResizeGestureHandler.updateMinSize(normalBounds.width(), normalBounds.height());
+ mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getExpandedBounds().width(),
+ mPipBoundsState.getExpandedBounds().height());
+ }
+ }
+
+ private void updatePinchResizeSizeConstraints(float aspectRatio) {
+ mPipBoundsState.updateMinMaxSize(aspectRatio);
+ mPipResizeGestureHandler.updateMinSize(mPipBoundsState.getMinSize().x,
+ mPipBoundsState.getMinSize().y);
+ mPipResizeGestureHandler.updateMaxSize(mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
+ }
+
+ /**
+ * TODO Add appropriate description
+ */
+ public void onRegistrationChanged(boolean isRegistered) {
+ if (isRegistered) {
+ // Register the accessibility connection.
+ } else {
+ mAccessibilityManager.setPictureInPictureActionReplacingConnection(null);
+ }
+ if (!isRegistered && mTouchState.isUserInteracting()) {
+ // If the input consumer is unregistered while the user is interacting, then we may not
+ // get the final TOUCH_UP event, so clean up the dismiss target as well
+ mPipDismissTargetHandler.cleanUpDismissTarget();
+ }
+ }
+
+ private void onAccessibilityShowMenu() {
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+
+ /**
+ * TODO Add appropriate description
+ */
+ public boolean handleTouchEvent(InputEvent inputEvent) {
+ // Skip any non motion events
+ if (!(inputEvent instanceof MotionEvent)) {
+ return true;
+ }
+
+ // do not process input event if not allowed
+ if (!mTouchState.getAllowInputEvents()) {
+ return true;
+ }
+
+ MotionEvent ev = (MotionEvent) inputEvent;
+ if (!mPipBoundsState.isStashed() && mPipResizeGestureHandler.willStartResizeGesture(ev)) {
+ // Initialize the touch state for the gesture, but immediately reset to invalidate the
+ // gesture
+ mTouchState.onTouchEvent(ev);
+ mTouchState.reset();
+ return true;
+ }
+
+ if (mPipResizeGestureHandler.hasOngoingGesture()) {
+ mGesture.cleanUpHighPerfSessionMaybe();
+ mPipDismissTargetHandler.hideDismissTargetMaybe();
+ return true;
+ }
+
+ if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting())
+ && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) {
+ // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
+ // to the touch state. Touch state needs a DOWN event in order to later process MOVE
+ // events it'll receive if the object is dragged out of the magnetic field.
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mTouchState.onTouchEvent(ev);
+ }
+
+ // Continue tracking velocity when the object is in the magnetic field, since we want to
+ // respect touch input velocity if the object is dragged out and then flung.
+ mTouchState.addMovementToVelocityTracker(ev);
+
+ return true;
+ }
+
+ if (!mTouchState.isUserInteracting()) {
+ ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Waiting to start the entry animation, skip the motion event.", TAG);
+ return true;
+ }
+
+ // Update the touch state
+ mTouchState.onTouchEvent(ev);
+
+ boolean shouldDeliverToMenu = mMenuState != MENU_STATE_NONE;
+
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ mGesture.onDown(mTouchState);
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ if (mGesture.onMove(mTouchState)) {
+ break;
+ }
+
+ shouldDeliverToMenu = !mTouchState.isDragging();
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ // Update the movement bounds again if the state has changed since the user started
+ // dragging (ie. when the IME shows)
+ updateMovementBounds();
+
+ if (mGesture.onUp(mTouchState)) {
+ break;
+ }
+ }
+ // Fall through to clean up
+ case MotionEvent.ACTION_CANCEL: {
+ shouldDeliverToMenu = !mTouchState.startedDragging() && !mTouchState.isDragging();
+ mTouchState.reset();
+ break;
+ }
+ case MotionEvent.ACTION_HOVER_ENTER: {
+ // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
+ // on and changing MotionEvents into HoverEvents.
+ // Let's not enable menu show/hide for a11y services.
+ if (!mAccessibilityManager.isTouchExplorationEnabled()) {
+ mTouchState.removeHoverExitTimeoutCallback();
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ false /* allowMenuTimeout */, false /* willResizeMenu */,
+ shouldShowResizeHandle());
+ }
+ }
+ // Fall through
+ case MotionEvent.ACTION_HOVER_MOVE: {
+ if (!shouldDeliverToMenu && !mSendingHoverAccessibilityEvents) {
+ sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ mSendingHoverAccessibilityEvents = true;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_HOVER_EXIT: {
+ // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
+ // on and changing MotionEvents into HoverEvents.
+ // Let's not enable menu show/hide for a11y services.
+ if (!mAccessibilityManager.isTouchExplorationEnabled()) {
+ mTouchState.scheduleHoverExitTimeoutCallback();
+ }
+ if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) {
+ sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ mSendingHoverAccessibilityEvents = false;
+ }
+ break;
+ }
+ }
+
+ shouldDeliverToMenu &= !mPipBoundsState.isStashed();
+
+ // Deliver the event to PipMenuActivity to handle button click if the menu has shown.
+ if (shouldDeliverToMenu) {
+ final MotionEvent cloneEvent = MotionEvent.obtain(ev);
+ // Send the cancel event and cancel menu timeout if it starts to drag.
+ if (mTouchState.startedDragging()) {
+ cloneEvent.setAction(MotionEvent.ACTION_CANCEL);
+ mMenuController.pokeMenu();
+ }
+
+ mMenuController.handlePointerEvent(cloneEvent);
+ cloneEvent.recycle();
+ }
+
+ return true;
+ }
+
+ private void sendAccessibilityHoverEvent(int type) {
+ if (!mAccessibilityManager.isEnabled()) {
+ return;
+ }
+
+ AccessibilityEvent event = AccessibilityEvent.obtain(type);
+ event.setImportantForAccessibility(true);
+ event.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID);
+ event.setWindowId(
+ AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+
+ /**
+ * Called when the PiP menu state is in the process of animating/changing from one to another.
+ */
+ private void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) {
+ if (mMenuState == menuState && !resize) {
+ return;
+ }
+
+ if (menuState == MENU_STATE_FULL && mMenuState != MENU_STATE_FULL) {
+ // Save the current snap fraction and if we do not drag or move the PiP, then
+ // we store back to this snap fraction. Otherwise, we'll reset the snap
+ // fraction and snap to the closest edge.
+ if (resize) {
+ // PIP is too small to show the menu actions and thus needs to be resized to a
+ // size that can fit them all. Resize to the default size.
+ animateToNormalSize(callback);
+ }
+ } else if (menuState == MENU_STATE_NONE && mMenuState == MENU_STATE_FULL) {
+ // Try and restore the PiP to the closest edge, using the saved snap fraction
+ // if possible
+ if (resize && !mPipResizeGestureHandler.isResizing()) {
+ if (mDeferResizeToNormalBoundsUntilRotation == -1) {
+ // This is a very special case: when the menu is expanded and visible,
+ // navigating to another activity can trigger auto-enter PiP, and if the
+ // revealed activity has a forced rotation set, then the controller will get
+ // updated with the new rotation of the display. However, at the same time,
+ // SystemUI will try to hide the menu by creating an animation to the normal
+ // bounds which are now stale. In such a case we defer the animation to the
+ // normal bounds until after the next onMovementBoundsChanged() call to get the
+ // bounds in the new orientation
+ int displayRotation = mContext.getDisplay().getRotation();
+ if (mDisplayRotation != displayRotation) {
+ mDeferResizeToNormalBoundsUntilRotation = displayRotation;
+ }
+ }
+
+ if (mDeferResizeToNormalBoundsUntilRotation == -1) {
+ animateToUnexpandedState(getUserResizeBounds());
+ }
+ } else {
+ mSavedSnapFraction = -1f;
+ }
+ }
+ }
+
+ private void setMenuState(int menuState) {
+ mMenuState = menuState;
+ updateMovementBounds();
+ // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip
+ // as well, or it can't handle a11y focus and pip menu can't perform any action.
+ onRegistrationChanged(menuState == MENU_STATE_NONE);
+ if (menuState == MENU_STATE_NONE) {
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_HIDE_MENU);
+ } else if (menuState == MENU_STATE_FULL) {
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_SHOW_MENU);
+ }
+ }
+
+ private void animateToMaximizedState(Runnable callback) {
+ Rect maxMovementBounds = new Rect();
+ Rect maxBounds = new Rect(0, 0, mPipBoundsState.getMaxSize().x,
+ mPipBoundsState.getMaxSize().y);
+ mPipBoundsAlgorithm.getMovementBounds(maxBounds, mInsetBounds, maxMovementBounds,
+ mIsImeShowing ? mImeHeight : 0);
+ mSavedSnapFraction = mMotionHelper.animateToExpandedState(maxBounds,
+ mPipBoundsState.getMovementBounds(), maxMovementBounds,
+ callback);
+ }
+
+ private void animateToNormalSize(Runnable callback) {
+ // Save the current bounds as the user-resize bounds.
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+
+ final Size minMenuSize = mMenuController.getEstimatedMinMenuSize();
+ final Rect normalBounds = mPipBoundsState.getNormalBounds();
+ final Rect destBounds = mPipBoundsAlgorithm.adjustNormalBoundsToFitMenu(normalBounds,
+ minMenuSize);
+ Rect restoredMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(destBounds,
+ mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
+ mSavedSnapFraction = mMotionHelper.animateToExpandedState(destBounds,
+ mPipBoundsState.getMovementBounds(), restoredMovementBounds, callback);
+ }
+
+ private void animateToUnexpandedState(Rect restoreBounds) {
+ Rect restoredMovementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(restoreBounds,
+ mInsetBounds, restoredMovementBounds, mIsImeShowing ? mImeHeight : 0);
+ mMotionHelper.animateToUnexpandedState(restoreBounds, mSavedSnapFraction,
+ restoredMovementBounds, mPipBoundsState.getMovementBounds(), false /* immediate */);
+ mSavedSnapFraction = -1f;
+ }
+
+ private void animateToUnStashedState() {
+ final Rect pipBounds = mPipBoundsState.getBounds();
+ final boolean onLeftEdge = pipBounds.left < mPipBoundsState.getDisplayBounds().left;
+ final Rect unStashedBounds = new Rect(0, pipBounds.top, 0, pipBounds.bottom);
+ unStashedBounds.left = onLeftEdge ? mInsetBounds.left
+ : mInsetBounds.right - pipBounds.width();
+ unStashedBounds.right = onLeftEdge ? mInsetBounds.left + pipBounds.width()
+ : mInsetBounds.right;
+ mMotionHelper.animateToUnStashedBounds(unStashedBounds);
+ }
+
+ /**
+ * @return the motion helper.
+ */
+ public PipMotionHelper getMotionHelper() {
+ return mMotionHelper;
+ }
+
+ @VisibleForTesting
+ public PipResizeGestureHandler getPipResizeGestureHandler() {
+ return mPipResizeGestureHandler;
+ }
+
+ @VisibleForTesting
+ public void setPipResizeGestureHandler(PipResizeGestureHandler pipResizeGestureHandler) {
+ mPipResizeGestureHandler = pipResizeGestureHandler;
+ }
+
+ @VisibleForTesting
+ public void setPipMotionHelper(PipMotionHelper pipMotionHelper) {
+ mMotionHelper = pipMotionHelper;
+ }
+
+ Rect getUserResizeBounds() {
+ return mPipResizeGestureHandler.getUserResizeBounds();
+ }
+
+ /**
+ * Sets the user resize bounds tracked by {@link PipResizeGestureHandler}
+ */
+ void setUserResizeBounds(Rect bounds) {
+ mPipResizeGestureHandler.setUserResizeBounds(bounds);
+ }
+
+ /**
+ * Gesture controlling normal movement of the PIP.
+ */
+ private class DefaultPipTouchGesture extends PipTouchGesture {
+ private final Point mStartPosition = new Point();
+ private final PointF mDelta = new PointF();
+ private boolean mShouldHideMenuAfterFling;
+
+ @Nullable private PipPerfHintController.PipHighPerfSession mPipHighPerfSession;
+
+ private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {}
+
+ @Override
+ public void cleanUpHighPerfSessionMaybe() {
+ if (mPipHighPerfSession != null) {
+ // Close the high perf session once pointer interactions are over;
+ mPipHighPerfSession.close();
+ mPipHighPerfSession = null;
+ }
+ }
+
+ @Override
+ public void onDown(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return;
+ }
+
+ if (mPipPerfHintController != null) {
+ // Cache the PiP high perf session to close it upon touch up.
+ mPipHighPerfSession = mPipPerfHintController.startSession(
+ this::onHighPerfSessionTimeout, "DefaultPipTouchGesture#onDown");
+ }
+
+ Rect bounds = getPossiblyMotionBounds();
+ mDelta.set(0f, 0f);
+ mStartPosition.set(bounds.left, bounds.top);
+ mMovementWithinDismiss = touchState.getDownTouchPosition().y
+ >= mPipBoundsState.getMovementBounds().bottom;
+ mMotionHelper.setSpringingToTouch(false);
+ // mPipDismissTargetHandler.setTaskLeash(mPipTaskOrganizer.getSurfaceControl());
+
+ // If the menu is still visible then just poke the menu
+ // so that it will timeout after the user stops touching it
+ if (mMenuState != MENU_STATE_NONE && !mPipBoundsState.isStashed()) {
+ mMenuController.pokeMenu();
+ }
+ }
+
+ @Override
+ public boolean onMove(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return false;
+ }
+
+ if (touchState.startedDragging()) {
+ mSavedSnapFraction = -1f;
+ mPipDismissTargetHandler.showDismissTargetMaybe();
+ }
+
+ if (touchState.isDragging()) {
+ mPipBoundsState.setHasUserMovedPip(true);
+
+ // Move the pinned stack freely
+ final PointF lastDelta = touchState.getLastTouchDelta();
+ float lastX = mStartPosition.x + mDelta.x;
+ float lastY = mStartPosition.y + mDelta.y;
+ float left = lastX + lastDelta.x;
+ float top = lastY + lastDelta.y;
+
+ // Add to the cumulative delta after bounding the position
+ mDelta.x += left - lastX;
+ mDelta.y += top - lastY;
+
+ mTmpBounds.set(getPossiblyMotionBounds());
+ mTmpBounds.offsetTo((int) left, (int) top);
+ mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
+
+ final PointF curPos = touchState.getLastTouchPosition();
+ if (mMovementWithinDismiss) {
+ // Track if movement remains near the bottom edge to identify swipe to dismiss
+ mMovementWithinDismiss = curPos.y >= mPipBoundsState.getMovementBounds().bottom;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onUp(PipTouchState touchState) {
+ mPipDismissTargetHandler.hideDismissTargetMaybe();
+ mPipDismissTargetHandler.setTaskLeash(null);
+
+ if (!touchState.isUserInteracting()) {
+ return false;
+ }
+
+ final PointF vel = touchState.getVelocity();
+
+ if (touchState.isDragging()) {
+ if (mMenuState != MENU_STATE_NONE) {
+ // If the menu is still visible, then just poke the menu so that
+ // it will timeout after the user stops touching it
+ mMenuController.showMenu(mMenuState, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ }
+ mShouldHideMenuAfterFling = mMenuState == MENU_STATE_NONE;
+
+ // Reset the touch state on up before the fling settles
+ mTouchState.reset();
+ if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) {
+ mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */);
+ } else {
+ if (mPipBoundsState.isStashed()) {
+ // Reset stashed state if previously stashed
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ }
+ mMotionHelper.flingToSnapTarget(vel.x, vel.y,
+ this::flingEndAction /* endAction */);
+ }
+ } else if (mTouchState.isDoubleTap() && !mPipBoundsState.isStashed()
+ && mMenuState != MENU_STATE_FULL) {
+ // If using pinch to zoom, double-tap functions as resizing between max/min size
+ if (mPipResizeGestureHandler.isUsingPinchToZoom()) {
+ final boolean toExpand = mPipBoundsState.getBounds().width()
+ < mPipBoundsState.getMaxSize().x
+ && mPipBoundsState.getBounds().height()
+ < mPipBoundsState.getMaxSize().y;
+ if (mMenuController.isMenuVisible()) {
+ mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
+ }
+
+ // the size to toggle to after a double tap
+ int nextSize = PipDoubleTapHelper
+ .nextSizeSpec(mPipBoundsState, getUserResizeBounds());
+
+ // actually toggle to the size chosen
+ if (nextSize == PipDoubleTapHelper.SIZE_SPEC_MAX) {
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+ animateToMaximizedState(null);
+ } else if (nextSize == PipDoubleTapHelper.SIZE_SPEC_DEFAULT) {
+ mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());
+ animateToNormalSize(null);
+ } else {
+ animateToUnexpandedState(getUserResizeBounds());
+ }
+ } else {
+ // Expand to fullscreen if this is a double tap
+ // the PiP should be frozen until the transition ends
+ setTouchEnabled(false);
+ mMotionHelper.expandLeavePip(false /* skipAnimation */);
+ }
+ } else if (mMenuState != MENU_STATE_FULL) {
+ if (mPipBoundsState.isStashed()) {
+ // Unstash immediately if stashed, and don't wait for the double tap timeout
+ animateToUnStashedState();
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_UNSTASHED);
+ mPipBoundsState.setStashed(STASH_TYPE_NONE);
+ mTouchState.removeDoubleTapTimeoutCallback();
+ } else if (!mTouchState.isWaitingForDoubleTap()) {
+ // User has stalled long enough for this not to be a drag or a double tap,
+ // just expand the menu
+ mMenuController.showMenu(MENU_STATE_FULL, mPipBoundsState.getBounds(),
+ true /* allowMenuTimeout */, willResizeMenu(),
+ shouldShowResizeHandle());
+ } else {
+ // Next touch event _may_ be the second tap for the double-tap, schedule a
+ // fallback runnable to trigger the menu if no touch event occurs before the
+ // next tap
+ mTouchState.scheduleDoubleTapTimeoutCallback();
+ }
+ }
+ cleanUpHighPerfSessionMaybe();
+ return true;
+ }
+
+ private void stashEndAction() {
+ if (mPipBoundsState.getBounds().left < 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_LEFT);
+ mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+ } else if (mPipBoundsState.getBounds().left >= 0
+ && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) {
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT);
+ mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+ }
+ mMenuController.hideMenu();
+ }
+
+ private void flingEndAction() {
+ if (mShouldHideMenuAfterFling) {
+ // If the menu is not visible, then we can still be showing the activity for the
+ // dismiss overlay, so just finish it after the animation completes
+ mMenuController.hideMenu();
+ }
+ }
+
+ private boolean shouldStash(PointF vel, Rect motionBounds) {
+ final boolean flingToLeft = vel.x < -mStashVelocityThreshold;
+ final boolean flingToRight = vel.x > mStashVelocityThreshold;
+ final int offset = motionBounds.width() / 2;
+ final boolean droppingOnLeft =
+ motionBounds.left < mPipBoundsState.getDisplayBounds().left - offset;
+ final boolean droppingOnRight =
+ motionBounds.right > mPipBoundsState.getDisplayBounds().right + offset;
+
+ // Do not allow stash if the destination edge contains display cutout. We only
+ // compare the left and right edges since we do not allow stash on top / bottom.
+ final DisplayCutout displayCutout =
+ mPipBoundsState.getDisplayLayout().getDisplayCutout();
+ if (displayCutout != null) {
+ if ((flingToLeft || droppingOnLeft)
+ && !displayCutout.getBoundingRectLeft().isEmpty()) {
+ return false;
+ } else if ((flingToRight || droppingOnRight)
+ && !displayCutout.getBoundingRectRight().isEmpty()) {
+ return false;
+ }
+ }
+
+ // If user flings the PIP window above the minimum velocity, stash PIP.
+ // Only allow stashing to the edge if PIP wasn't previously stashed on the opposite
+ // edge.
+ final boolean stashFromFlingToEdge =
+ (flingToLeft && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT)
+ || (flingToRight && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT);
+
+ // If User releases the PIP window while it's out of the display bounds, put
+ // PIP into stashed mode.
+ final boolean stashFromDroppingOnEdge = droppingOnLeft || droppingOnRight;
+
+ return stashFromFlingToEdge || stashFromDroppingOnEdge;
+ }
+ }
+
+ /**
+ * Updates the current movement bounds based on whether the menu is currently visible and
+ * resized.
+ */
+ private void updateMovementBounds() {
+ mPipBoundsAlgorithm.getMovementBounds(mPipBoundsState.getBounds(),
+ mInsetBounds, mPipBoundsState.getMovementBounds(), mIsImeShowing ? mImeHeight : 0);
+ mMotionHelper.onMovementBoundsChanged();
+ }
+
+ private Rect getMovementBounds(Rect curBounds) {
+ Rect movementBounds = new Rect();
+ mPipBoundsAlgorithm.getMovementBounds(curBounds, mInsetBounds,
+ movementBounds, mIsImeShowing ? mImeHeight : 0);
+ return movementBounds;
+ }
+
+ /**
+ * @return {@code true} if the menu should be resized on tap because app explicitly specifies
+ * PiP window size that is too small to hold all the actions.
+ */
+ private boolean willResizeMenu() {
+ if (!mEnableResize) {
+ return false;
+ }
+ final Size estimatedMinMenuSize = mMenuController.getEstimatedMinMenuSize();
+ if (estimatedMinMenuSize == null) {
+ ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to get estimated menu size", TAG);
+ return false;
+ }
+ final Rect currentBounds = mPipBoundsState.getBounds();
+ return currentBounds.width() < estimatedMinMenuSize.getWidth()
+ || currentBounds.height() < estimatedMinMenuSize.getHeight();
+ }
+
+ /**
+ * Returns the PIP bounds if we're not in the middle of a motion operation, or the current,
+ * temporary motion bounds otherwise.
+ */
+ Rect getPossiblyMotionBounds() {
+ return mPipBoundsState.getMotionBoundsState().isInMotion()
+ ? mPipBoundsState.getMotionBoundsState().getBoundsInMotion()
+ : mPipBoundsState.getBounds();
+ }
+
+ void setOhmOffset(int offset) {
+ mPipResizeGestureHandler.setOhmOffset(offset);
+ }
+
+ /**
+ * Dumps the {@link PipTouchHandler} state.
+ */
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mMenuState=" + mMenuState);
+ pw.println(innerPrefix + "mIsImeShowing=" + mIsImeShowing);
+ pw.println(innerPrefix + "mImeHeight=" + mImeHeight);
+ pw.println(innerPrefix + "mIsShelfShowing=" + mIsShelfShowing);
+ pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight);
+ pw.println(innerPrefix + "mSavedSnapFraction=" + mSavedSnapFraction);
+ pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
+ mPipBoundsAlgorithm.dump(pw, innerPrefix);
+ mTouchState.dump(pw, innerPrefix);
+ if (mPipResizeGestureHandler != null) {
+ mPipResizeGestureHandler.dump(pw, innerPrefix);
+ }
+ }
+
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
index b94989d..12e395d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
@@ -24,6 +24,7 @@
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.helpers.WindowUtils
import android.tools.traces.parsers.toFlickerComponent
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -143,6 +144,10 @@
}
}
+ @FlakyTest(bugId = 293133362)
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 48e396a..6be411d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -1222,6 +1222,19 @@
assertThat(update.bubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT);
}
+ @Test
+ public void setSelectedBubbleAndExpandStack() {
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ mBubbleData.setListener(mListener);
+
+ mBubbleData.setSelectedBubbleAndExpandStack(mBubbleA1);
+
+ verifyUpdateReceived();
+ assertSelectionChangedTo(mBubbleA1);
+ assertExpandedChangedTo(true);
+ }
+
private void verifyUpdateReceived() {
verify(mListener).applyUpdate(mUpdateCaptor.capture());
reset(mListener);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
index 3d5cd69..85f1da5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
@@ -16,33 +16,26 @@
package com.android.wm.shell.pip.phone;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
-import android.os.SystemProperties;
import android.testing.AndroidTestingRunner;
import android.util.Size;
import android.view.DisplayInfo;
-import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.SizeSpecSource;
-import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.exceptions.misusing.InvalidUseOfMatchersException;
import java.util.HashMap;
import java.util.Map;
@@ -63,15 +56,24 @@
private static final float DEFAULT_PERCENT = 0.6f;
/** Minimum sizing percentage */
private static final float MIN_PERCENT = 0.5f;
+ /** Threshold to determine if a Display is square-ish. */
+ private static final float SQUARE_DISPLAY_THRESHOLD = 0.95f;
+ /** Default sizing percentage for square-ish Display. */
+ private static final float SQUARE_DISPLAY_DEFAULT_PERCENT = 0.5f;
+ /** Minimum sizing percentage for square-ish Display. */
+ private static final float SQUARE_DISPLAY_MIN_PERCENT = 0.4f;
/** Aspect ratio that the new PIP size spec logic optimizes for. */
private static final float OPTIMIZED_ASPECT_RATIO = 9f / 16;
- /** A map of aspect ratios to be tested to expected sizes */
- private static Map<Float, Size> sExpectedMaxSizes;
- private static Map<Float, Size> sExpectedDefaultSizes;
- private static Map<Float, Size> sExpectedMinSizes;
- /** A static mockito session object to mock {@link SystemProperties} */
- private static StaticMockitoSession sStaticMockitoSession;
+ /** Maps of aspect ratios to be tested to expected sizes on non-square Display. */
+ private static Map<Float, Size> sNonSquareDisplayExpectedMaxSizes;
+ private static Map<Float, Size> sNonSquareDisplayExpectedDefaultSizes;
+ private static Map<Float, Size> sNonSquareDisplayExpectedMinSizes;
+
+ /** Maps of aspect ratios to be tested to expected sizes on square Display. */
+ private static Map<Float, Size> sSquareDisplayExpectedMaxSizes;
+ private static Map<Float, Size> sSquareDisplayExpectedDefaultSizes;
+ private static Map<Float, Size> sSquareDisplayExpectedMinSizes;
@Mock private Context mContext;
@Mock private Resources mResources;
@@ -80,49 +82,55 @@
private SizeSpecSource mSizeSpecSource;
/**
- * Sets up static Mockito session for SystemProperties and mocks necessary static methods.
+ * Initializes the map with the aspect ratios to be tested and corresponding expected max sizes.
+ * This is to initialize the expectations on non-square Display only.
*/
- private static void setUpStaticSystemPropertiesSession() {
- sStaticMockitoSession = mockitoSession()
- .mockStatic(SystemProperties.class).startMocking();
- when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
- String property = invocation.getArgument(0);
- if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
- return Float.toString(DEFAULT_PERCENT);
- } else if (property.equals("com.android.wm.shell.pip.phone.min_percentage")) {
- return Float.toString(MIN_PERCENT);
- }
+ private static void initNonSquareDisplayExpectedSizes() {
+ sNonSquareDisplayExpectedMaxSizes = new HashMap<>();
+ sNonSquareDisplayExpectedDefaultSizes = new HashMap<>();
+ sNonSquareDisplayExpectedMinSizes = new HashMap<>();
- // throw an exception if illegal arguments are used for these tests
- throw new InvalidUseOfMatchersException(
- String.format("Argument %s does not match", property)
- );
- });
+ sNonSquareDisplayExpectedMaxSizes.put(16f / 9, new Size(1000, 563));
+ sNonSquareDisplayExpectedDefaultSizes.put(16f / 9, new Size(600, 338));
+ sNonSquareDisplayExpectedMinSizes.put(16f / 9, new Size(501, 282));
+
+ sNonSquareDisplayExpectedMaxSizes.put(4f / 3, new Size(893, 670));
+ sNonSquareDisplayExpectedDefaultSizes.put(4f / 3, new Size(536, 402));
+ sNonSquareDisplayExpectedMinSizes.put(4f / 3, new Size(447, 335));
+
+ sNonSquareDisplayExpectedMaxSizes.put(3f / 4, new Size(670, 893));
+ sNonSquareDisplayExpectedDefaultSizes.put(3f / 4, new Size(402, 536));
+ sNonSquareDisplayExpectedMinSizes.put(3f / 4, new Size(335, 447));
+
+ sNonSquareDisplayExpectedMaxSizes.put(9f / 16, new Size(563, 1001));
+ sNonSquareDisplayExpectedDefaultSizes.put(9f / 16, new Size(338, 601));
+ sNonSquareDisplayExpectedMinSizes.put(9f / 16, new Size(282, 501));
}
/**
* Initializes the map with the aspect ratios to be tested and corresponding expected max sizes.
+ * This is to initialize the expectations on square Display only.
*/
- private static void initExpectedSizes() {
- sExpectedMaxSizes = new HashMap<>();
- sExpectedDefaultSizes = new HashMap<>();
- sExpectedMinSizes = new HashMap<>();
+ private static void initSquareDisplayExpectedSizes() {
+ sSquareDisplayExpectedMaxSizes = new HashMap<>();
+ sSquareDisplayExpectedDefaultSizes = new HashMap<>();
+ sSquareDisplayExpectedMinSizes = new HashMap<>();
- sExpectedMaxSizes.put(16f / 9, new Size(1000, 563));
- sExpectedDefaultSizes.put(16f / 9, new Size(600, 338));
- sExpectedMinSizes.put(16f / 9, new Size(501, 282));
+ sSquareDisplayExpectedMaxSizes.put(16f / 9, new Size(1000, 563));
+ sSquareDisplayExpectedDefaultSizes.put(16f / 9, new Size(500, 281));
+ sSquareDisplayExpectedMinSizes.put(16f / 9, new Size(400, 225));
- sExpectedMaxSizes.put(4f / 3, new Size(893, 670));
- sExpectedDefaultSizes.put(4f / 3, new Size(536, 402));
- sExpectedMinSizes.put(4f / 3, new Size(447, 335));
+ sSquareDisplayExpectedMaxSizes.put(4f / 3, new Size(893, 670));
+ sSquareDisplayExpectedDefaultSizes.put(4f / 3, new Size(447, 335));
+ sSquareDisplayExpectedMinSizes.put(4f / 3, new Size(357, 268));
- sExpectedMaxSizes.put(3f / 4, new Size(670, 893));
- sExpectedDefaultSizes.put(3f / 4, new Size(402, 536));
- sExpectedMinSizes.put(3f / 4, new Size(335, 447));
+ sSquareDisplayExpectedMaxSizes.put(3f / 4, new Size(670, 893));
+ sSquareDisplayExpectedDefaultSizes.put(3f / 4, new Size(335, 447));
+ sSquareDisplayExpectedMinSizes.put(3f / 4, new Size(268, 357));
- sExpectedMaxSizes.put(9f / 16, new Size(563, 1001));
- sExpectedDefaultSizes.put(9f / 16, new Size(338, 601));
- sExpectedMinSizes.put(9f / 16, new Size(282, 501));
+ sSquareDisplayExpectedMaxSizes.put(9f / 16, new Size(563, 1001));
+ sSquareDisplayExpectedDefaultSizes.put(9f / 16, new Size(282, 501));
+ sSquareDisplayExpectedMinSizes.put(9f / 16, new Size(225, 400));
}
private void forEveryTestCaseCheck(Map<Float, Size> expectedSizes,
@@ -137,20 +145,38 @@
@Before
public void setUp() {
- initExpectedSizes();
+ initNonSquareDisplayExpectedSizes();
+ initSquareDisplayExpectedSizes();
- when(mResources.getDimensionPixelSize(anyInt())).thenReturn(DEFAULT_MIN_EDGE_SIZE);
- when(mResources.getFloat(anyInt())).thenReturn(OPTIMIZED_ASPECT_RATIO);
- when(mResources.getString(anyInt())).thenReturn("0x0");
+ when(mResources.getFloat(R.dimen.config_pipSystemPreferredDefaultSizePercent))
+ .thenReturn(DEFAULT_PERCENT);
+ when(mResources.getFloat(R.dimen.config_pipSystemPreferredMinimumSizePercent))
+ .thenReturn(MIN_PERCENT);
+ when(mResources.getDimensionPixelSize(R.dimen.default_minimal_size_pip_resizable_task))
+ .thenReturn(DEFAULT_MIN_EDGE_SIZE);
+ when(mResources.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio))
+ .thenReturn(OPTIMIZED_ASPECT_RATIO);
+ when(mResources.getString(R.string.config_defaultPictureInPictureScreenEdgeInsets))
+ .thenReturn("0x0");
when(mResources.getDisplayMetrics())
.thenReturn(getContext().getResources().getDisplayMetrics());
+ when(mResources.getFloat(R.dimen.config_pipSquareDisplayThresholdForSystemPreferredSize))
+ .thenReturn(SQUARE_DISPLAY_THRESHOLD);
+ when(mResources.getFloat(
+ R.dimen.config_pipSystemPreferredDefaultSizePercentForSquareDisplay))
+ .thenReturn(SQUARE_DISPLAY_DEFAULT_PERCENT);
+ when(mResources.getFloat(
+ R.dimen.config_pipSystemPreferredMinimumSizePercentForSquareDisplay))
+ .thenReturn(SQUARE_DISPLAY_MIN_PERCENT);
// set up the mock context for spec handler specifically
when(mContext.getResources()).thenReturn(mResources);
+ }
+ private void setupSizeSpecWithDisplayDimension(int width, int height) {
DisplayInfo displayInfo = new DisplayInfo();
- displayInfo.logicalWidth = DISPLAY_EDGE_SIZE;
- displayInfo.logicalHeight = DISPLAY_EDGE_SIZE;
+ displayInfo.logicalWidth = width;
+ displayInfo.logicalHeight = height;
// use the parent context (not the mocked one) to obtain the display layout
// this is done to avoid unnecessary mocking while allowing for custom display dimensions
@@ -159,38 +185,57 @@
mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
mPipDisplayLayoutState.setDisplayLayout(displayLayout);
- setUpStaticSystemPropertiesSession();
mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
// no overridden min edge size by default
mSizeSpecSource.setOverrideMinSize(null);
}
- @After
- public void cleanUp() {
- sStaticMockitoSession.finishMocking();
- }
-
@Test
- public void testGetMaxSize() {
- forEveryTestCaseCheck(sExpectedMaxSizes,
+ public void testGetMaxSize_nonSquareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sNonSquareDisplayExpectedMaxSizes,
(aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio));
}
@Test
- public void testGetDefaultSize() {
- forEveryTestCaseCheck(sExpectedDefaultSizes,
+ public void testGetDefaultSize_nonSquareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sNonSquareDisplayExpectedDefaultSizes,
(aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio));
}
@Test
- public void testGetMinSize() {
- forEveryTestCaseCheck(sExpectedMinSizes,
+ public void testGetMinSize_nonSquareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sNonSquareDisplayExpectedMinSizes,
+ (aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio));
+ }
+
+ @Test
+ public void testGetMaxSize_squareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sSquareDisplayExpectedMaxSizes,
+ (aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio));
+ }
+
+ @Test
+ public void testGetDefaultSize_squareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sSquareDisplayExpectedDefaultSizes,
+ (aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio));
+ }
+
+ @Test
+ public void testGetMinSize_squareDisplay() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE, DISPLAY_EDGE_SIZE);
+ forEveryTestCaseCheck(sSquareDisplayExpectedMinSizes,
(aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio));
}
@Test
public void testGetSizeForAspectRatio_noOverrideMinSize() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE);
// an initial size with 16:9 aspect ratio
Size initSize = new Size(600, 337);
@@ -202,6 +247,7 @@
@Test
public void testGetSizeForAspectRatio_withOverrideMinSize() {
+ setupSizeSpecWithDisplayDimension(DISPLAY_EDGE_SIZE * 2, DISPLAY_EDGE_SIZE);
// an initial size with a 1:1 aspect ratio
Size initSize = new Size(OVERRIDE_MIN_EDGE_SIZE, OVERRIDE_MIN_EDGE_SIZE);
mSizeSpecSource.setOverrideMinSize(initSize);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 02fa5f2..29bb1b9 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -79,13 +79,13 @@
include_dirs: [
"external/skia/include/private",
"external/skia/src/core",
+ "external/skia/src/utils",
],
target: {
android: {
include_dirs: [
"external/skia/src/image",
- "external/skia/src/utils",
"external/skia/src/gpu",
"external/skia/src/shaders",
],
@@ -530,7 +530,9 @@
"effects/GainmapRenderer.cpp",
"pipeline/skia/BackdropFilterDrawable.cpp",
"pipeline/skia/HolePunch.cpp",
+ "pipeline/skia/SkiaCpuPipeline.cpp",
"pipeline/skia/SkiaDisplayList.cpp",
+ "pipeline/skia/SkiaPipeline.cpp",
"pipeline/skia/SkiaRecordingCanvas.cpp",
"pipeline/skia/StretchMask.cpp",
"pipeline/skia/RenderNodeDrawable.cpp",
@@ -569,6 +571,7 @@
"HWUIProperties.sysprop",
"Interpolator.cpp",
"JankTracker.cpp",
+ "LayerUpdateQueue.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
"Mesh.cpp",
@@ -605,9 +608,9 @@
"pipeline/skia/GLFunctorDrawable.cpp",
"pipeline/skia/LayerDrawable.cpp",
"pipeline/skia/ShaderCache.cpp",
+ "pipeline/skia/SkiaGpuPipeline.cpp",
"pipeline/skia/SkiaMemoryTracer.cpp",
"pipeline/skia/SkiaOpenGLPipeline.cpp",
- "pipeline/skia/SkiaPipeline.cpp",
"pipeline/skia/SkiaProfileRenderer.cpp",
"pipeline/skia/SkiaVulkanPipeline.cpp",
"pipeline/skia/VkFunctorDrawable.cpp",
@@ -631,7 +634,6 @@
"DeferredLayerUpdater.cpp",
"HardwareBitmapUploader.cpp",
"Layer.cpp",
- "LayerUpdateQueue.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
"TreeInfo.cpp",
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index ec53070..c1510d9 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -242,7 +242,7 @@
enum class OverdrawColorSet { Default = 0, Deuteranomaly };
-enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 };
+enum class RenderPipelineType { SkiaGL, SkiaVulkan, SkiaCpu, NotInitialized = 128 };
enum class StretchEffectBehavior {
ShaderHWUI, // Stretch shader in HWUI only, matrix scale in SF
diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp
new file mode 100644
index 0000000..5bbbc10
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaCpuPipeline.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pipeline/skia/SkiaCpuPipeline.h"
+
+#include <system/window.h>
+
+#include "DeviceInfo.h"
+#include "LightingInfo.h"
+#include "renderthread/Frame.h"
+#include "utils/Color.h"
+
+using namespace android::uirenderer::renderthread;
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+void SkiaCpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
+ // Render all layers that need to be updated, in order.
+ for (size_t i = 0; i < layers.entries().size(); i++) {
+ RenderNode* layerNode = layers.entries()[i].renderNode.get();
+ // only schedule repaint if node still on layer - possible it may have been
+ // removed during a dropped frame, but layers may still remain scheduled so
+ // as not to lose info on what portion is damaged
+ if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
+ continue;
+ }
+ bool rendered = renderLayerImpl(layerNode, layers.entries()[i].damage);
+ if (!rendered) {
+ return;
+ }
+ }
+}
+
+// If the given node didn't have a layer surface, or had one of the wrong size, this method
+// creates a new one and returns true. Otherwise does nothing and returns false.
+bool SkiaCpuPipeline::createOrUpdateLayer(RenderNode* node,
+ const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) {
+ // compute the size of the surface (i.e. texture) to be allocated for this layer
+ const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
+ const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
+
+ SkSurface* layer = node->getLayerSurface();
+ if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
+ SkImageInfo info;
+ info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
+ kPremul_SkAlphaType, getSurfaceColorSpace());
+ SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+ node->setLayerSurface(SkSurfaces::Raster(info, &props));
+ if (node->getLayerSurface()) {
+ // update the transform in window of the layer to reset its origin wrt light source
+ // position
+ Matrix4 windowTransform;
+ damageAccumulator.computeCurrentTransform(&windowTransform);
+ node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+ } else {
+ String8 cachesOutput;
+ mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
+ &mRenderThread.renderState());
+ ALOGE("%s", cachesOutput.c_str());
+ if (errorHandler) {
+ std::ostringstream err;
+ err << "Unable to create layer for " << node->getName();
+ const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
+ err << ", size " << info.width() << "x" << info.height() << " max size "
+ << maxTextureSize << " color type " << (int)info.colorType() << " has context "
+ << (int)(mRenderThread.getGrContext() != nullptr);
+ errorHandler->onError(err.str());
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+MakeCurrentResult SkiaCpuPipeline::makeCurrent() {
+ return MakeCurrentResult::AlreadyCurrent;
+}
+
+Frame SkiaCpuPipeline::getFrame() {
+ return Frame(mSurface->width(), mSurface->height(), 0);
+}
+
+IRenderPipeline::DrawResult SkiaCpuPipeline::draw(
+ const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+ const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
+ LightingInfo::updateLighting(lightGeometry, lightInfo);
+ renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, mSurface,
+ SkMatrix::I());
+ return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}};
+}
+
+bool SkiaCpuPipeline::setSurface(ANativeWindow* surface, SwapBehavior swapBehavior) {
+ if (surface) {
+ ANativeWindowBuffer* buffer;
+ surface->dequeueBuffer(surface, &buffer, nullptr);
+ int width, height;
+ surface->query(surface, NATIVE_WINDOW_WIDTH, &width);
+ surface->query(surface, NATIVE_WINDOW_HEIGHT, &height);
+ SkImageInfo imageInfo =
+ SkImageInfo::Make(width, height, mSurfaceColorType,
+ SkAlphaType::kPremul_SkAlphaType, mSurfaceColorSpace);
+ size_t widthBytes = width * imageInfo.bytesPerPixel();
+ void* pixels = buffer->reserved[0];
+ mSurface = SkSurfaces::WrapPixels(imageInfo, pixels, widthBytes);
+ } else {
+ mSurface = sk_sp<SkSurface>();
+ }
+ return true;
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaCpuPipeline.h b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h
new file mode 100644
index 0000000..5a1014c
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaCpuPipeline.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaCpuPipeline : public SkiaPipeline {
+public:
+ SkiaCpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {}
+ ~SkiaCpuPipeline() {}
+
+ bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
+ bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+ void unpinImages() override {}
+
+ // If the given node didn't have a layer surface, or had one of the wrong size, this method
+ // creates a new one and returns true. Otherwise does nothing and returns false.
+ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) override;
+ void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override;
+ void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {}
+ bool hasHardwareBuffer() override { return false; }
+
+ renderthread::MakeCurrentResult makeCurrent() override;
+ renderthread::Frame getFrame() override;
+ renderthread::IRenderPipeline::DrawResult draw(
+ const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+ const renderthread::HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) override;
+ bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) override {
+ return false;
+ }
+ DeferredLayerUpdater* createTextureLayer() override { return nullptr; }
+ bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
+ [[nodiscard]] android::base::unique_fd flush() override {
+ return android::base::unique_fd(-1);
+ };
+ void onStop() override {}
+ bool isSurfaceReady() override { return mSurface.get() != nullptr; }
+ bool isContextReady() override { return true; }
+
+ const SkM44& getPixelSnapMatrix() const override {
+ static const SkM44 sSnapMatrix = SkM44();
+ return sSnapMatrix;
+ }
+
+private:
+ sk_sp<SkSurface> mSurface;
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp
new file mode 100644
index 0000000..7bfbfdc
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaGpuPipeline.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+#include <SkImageAndroid.h>
+#include <gui/TraceUtils.h>
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+
+using namespace android::uirenderer::renderthread;
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+SkiaGpuPipeline::SkiaGpuPipeline(RenderThread& thread) : SkiaPipeline(thread) {}
+
+SkiaGpuPipeline::~SkiaGpuPipeline() {
+ unpinImages();
+}
+
+void SkiaGpuPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
+ sk_sp<GrDirectContext> cachedContext;
+
+ // Render all layers that need to be updated, in order.
+ for (size_t i = 0; i < layers.entries().size(); i++) {
+ RenderNode* layerNode = layers.entries()[i].renderNode.get();
+ // only schedule repaint if node still on layer - possible it may have been
+ // removed during a dropped frame, but layers may still remain scheduled so
+ // as not to lose info on what portion is damaged
+ if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
+ continue;
+ }
+ bool rendered = renderLayerImpl(layerNode, layers.entries()[i].damage);
+ if (!rendered) {
+ return;
+ }
+ // cache the current context so that we can defer flushing it until
+ // either all the layers have been rendered or the context changes
+ GrDirectContext* currentContext =
+ GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
+ if (cachedContext.get() != currentContext) {
+ if (cachedContext.get()) {
+ ATRACE_NAME("flush layers (context changed)");
+ cachedContext->flushAndSubmit();
+ }
+ cachedContext.reset(SkSafeRef(currentContext));
+ }
+ }
+ if (cachedContext.get()) {
+ ATRACE_NAME("flush layers");
+ cachedContext->flushAndSubmit();
+ }
+}
+
+// If the given node didn't have a layer surface, or had one of the wrong size, this method
+// creates a new one and returns true. Otherwise does nothing and returns false.
+bool SkiaGpuPipeline::createOrUpdateLayer(RenderNode* node,
+ const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) {
+ // compute the size of the surface (i.e. texture) to be allocated for this layer
+ const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
+ const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
+
+ SkSurface* layer = node->getLayerSurface();
+ if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
+ SkImageInfo info;
+ info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
+ kPremul_SkAlphaType, getSurfaceColorSpace());
+ SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+ SkASSERT(mRenderThread.getGrContext() != nullptr);
+ node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+ skgpu::Budgeted::kYes, info, 0,
+ this->getSurfaceOrigin(), &props));
+ if (node->getLayerSurface()) {
+ // update the transform in window of the layer to reset its origin wrt light source
+ // position
+ Matrix4 windowTransform;
+ damageAccumulator.computeCurrentTransform(&windowTransform);
+ node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+ } else {
+ String8 cachesOutput;
+ mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
+ &mRenderThread.renderState());
+ ALOGE("%s", cachesOutput.c_str());
+ if (errorHandler) {
+ std::ostringstream err;
+ err << "Unable to create layer for " << node->getName();
+ const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
+ err << ", size " << info.width() << "x" << info.height() << " max size "
+ << maxTextureSize << " color type " << (int)info.colorType() << " has context "
+ << (int)(mRenderThread.getGrContext() != nullptr);
+ errorHandler->onError(err.str());
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+bool SkiaGpuPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
+ if (!mRenderThread.getGrContext()) {
+ ALOGD("Trying to pin an image with an invalid GrContext");
+ return false;
+ }
+ for (SkImage* image : mutableImages) {
+ if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
+ mPinnedImages.emplace_back(sk_ref_sp(image));
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+void SkiaGpuPipeline::unpinImages() {
+ for (auto& image : mPinnedImages) {
+ skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
+ }
+ mPinnedImages.clear();
+}
+
+void SkiaGpuPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
+ GrDirectContext* context = thread.getGrContext();
+ if (context && !bitmap->isHardware()) {
+ ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
+ auto image = bitmap->makeImage();
+ if (image.get()) {
+ skgpu::ganesh::PinAsTexture(context, image.get());
+ skgpu::ganesh::UnpinTexture(context, image.get());
+ // A submit is necessary as there may not be a frame coming soon, so without a call
+ // to submit these texture uploads can just sit in the queue building up until
+ // we run out of RAM
+ context->flushAndSubmit();
+ }
+ }
+}
+
+sk_sp<SkSurface> SkiaGpuPipeline::getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams) {
+ auto bufferColorSpace = bufferParams.getColorSpace();
+ if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
+ !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
+ mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
+ mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
+ bufferColorSpace, nullptr, true);
+ mBufferColorSpace = bufferColorSpace;
+ }
+ return mBufferSurface;
+}
+
+void SkiaGpuPipeline::dumpResourceCacheUsage() const {
+ int resources;
+ size_t bytes;
+ mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes);
+ size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit();
+
+ SkString log("Resource Cache Usage:\n");
+ log.appendf("%8d items\n", resources);
+ log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes,
+ bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f)));
+
+ ALOGD("%s", log.c_str());
+}
+
+void SkiaGpuPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
+ if (mHardwareBuffer) {
+ AHardwareBuffer_release(mHardwareBuffer);
+ mHardwareBuffer = nullptr;
+ }
+
+ if (buffer) {
+ AHardwareBuffer_acquire(buffer);
+ mHardwareBuffer = buffer;
+ }
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index c8d5987..e4b1f91 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -14,25 +14,25 @@
* limitations under the License.
*/
-#include "SkiaOpenGLPipeline.h"
+#include "pipeline/skia/SkiaOpenGLPipeline.h"
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
-#include <include/gpu/gl/GrGLTypes.h>
#include <GrBackendSurface.h>
#include <SkBlendMode.h>
#include <SkImageInfo.h>
#include <cutils/properties.h>
#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/gl/GrGLTypes.h>
#include <strings.h>
#include "DeferredLayerUpdater.h"
#include "FrameInfo.h"
-#include "LayerDrawable.h"
#include "LightingInfo.h"
-#include "SkiaPipeline.h"
-#include "SkiaProfileRenderer.h"
#include "hwui/Bitmap.h"
+#include "pipeline/skia/LayerDrawable.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
+#include "pipeline/skia/SkiaProfileRenderer.h"
#include "private/hwui/DrawGlInfo.h"
#include "renderstate/RenderState.h"
#include "renderthread/EglManager.h"
@@ -47,7 +47,7 @@
namespace skiapipeline {
SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread)
- : SkiaPipeline(thread), mEglManager(thread.eglManager()) {
+ : SkiaGpuPipeline(thread), mEglManager(thread.eglManager()) {
thread.renderState().registerContextCallback(this);
}
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 99469d1..34932b1 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaPipeline.h"
-#include <include/android/SkSurfaceAndroid.h>
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include <include/encode/SkPngEncoder.h>
#include <SkCanvas.h>
#include <SkColor.h>
#include <SkColorSpace.h>
@@ -40,6 +37,9 @@
#include <SkTypeface.h>
#include <android-base/properties.h>
#include <gui/TraceUtils.h>
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/encode/SkPngEncoder.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <unistd.h>
#include <sstream>
@@ -62,37 +62,13 @@
setSurfaceColorProperties(mColorMode);
}
-SkiaPipeline::~SkiaPipeline() {
- unpinImages();
-}
+SkiaPipeline::~SkiaPipeline() {}
void SkiaPipeline::onDestroyHardwareResources() {
unpinImages();
mRenderThread.cacheManager().trimStaleResources();
}
-bool SkiaPipeline::pinImages(std::vector<SkImage*>& mutableImages) {
- if (!mRenderThread.getGrContext()) {
- ALOGD("Trying to pin an image with an invalid GrContext");
- return false;
- }
- for (SkImage* image : mutableImages) {
- if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
- mPinnedImages.emplace_back(sk_ref_sp(image));
- } else {
- return false;
- }
- }
- return true;
-}
-
-void SkiaPipeline::unpinImages() {
- for (auto& image : mPinnedImages) {
- skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
- }
- mPinnedImages.clear();
-}
-
void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry,
LayerUpdateQueue* layerUpdateQueue, bool opaque,
const LightInfo& lightInfo) {
@@ -102,136 +78,48 @@
layerUpdateQueue->clear();
}
-void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
- sk_sp<GrDirectContext> cachedContext;
-
- // Render all layers that need to be updated, in order.
- for (size_t i = 0; i < layers.entries().size(); i++) {
- RenderNode* layerNode = layers.entries()[i].renderNode.get();
- // only schedule repaint if node still on layer - possible it may have been
- // removed during a dropped frame, but layers may still remain scheduled so
- // as not to lose info on what portion is damaged
- if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
- continue;
- }
- SkASSERT(layerNode->getLayerSurface());
- SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl();
- if (!displayList || displayList->isEmpty()) {
- ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
- return;
- }
-
- const Rect& layerDamage = layers.entries()[i].damage;
-
- SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
-
- int saveCount = layerCanvas->save();
- SkASSERT(saveCount == 1);
-
- layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
-
- // TODO: put localized light center calculation and storage to a drawable related code.
- // It does not seem right to store something localized in a global state
- // fix here and in recordLayers
- const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
- Vector3 transformedLightCenter(savedLightCenter);
- // map current light center into RenderNode's coordinate space
- layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
- LightingInfo::setLightCenterRaw(transformedLightCenter);
-
- const RenderProperties& properties = layerNode->properties();
- const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
- if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
- return;
- }
-
- ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
- bounds.height());
-
- layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
- layerCanvas->clear(SK_ColorTRANSPARENT);
-
- RenderNodeDrawable root(layerNode, layerCanvas, false);
- root.forceDraw(layerCanvas);
- layerCanvas->restoreToCount(saveCount);
-
- LightingInfo::setLightCenterRaw(savedLightCenter);
-
- // cache the current context so that we can defer flushing it until
- // either all the layers have been rendered or the context changes
- GrDirectContext* currentContext =
- GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
- if (cachedContext.get() != currentContext) {
- if (cachedContext.get()) {
- ATRACE_NAME("flush layers (context changed)");
- cachedContext->flushAndSubmit();
- }
- cachedContext.reset(SkSafeRef(currentContext));
- }
+bool SkiaPipeline::renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage) {
+ SkASSERT(layerNode->getLayerSurface());
+ SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl();
+ if (!displayList || displayList->isEmpty()) {
+ ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
+ return false;
}
- if (cachedContext.get()) {
- ATRACE_NAME("flush layers");
- cachedContext->flushAndSubmit();
- }
-}
+ SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();
-bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
- ErrorHandler* errorHandler) {
- // compute the size of the surface (i.e. texture) to be allocated for this layer
- const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
- const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;
+ int saveCount = layerCanvas->save();
+ SkASSERT(saveCount == 1);
- SkSurface* layer = node->getLayerSurface();
- if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
- SkImageInfo info;
- info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
- kPremul_SkAlphaType, getSurfaceColorSpace());
- SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
- SkASSERT(mRenderThread.getGrContext() != nullptr);
- node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
- skgpu::Budgeted::kYes, info, 0,
- this->getSurfaceOrigin(), &props));
- if (node->getLayerSurface()) {
- // update the transform in window of the layer to reset its origin wrt light source
- // position
- Matrix4 windowTransform;
- damageAccumulator.computeCurrentTransform(&windowTransform);
- node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
- } else {
- String8 cachesOutput;
- mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
- &mRenderThread.renderState());
- ALOGE("%s", cachesOutput.c_str());
- if (errorHandler) {
- std::ostringstream err;
- err << "Unable to create layer for " << node->getName();
- const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
- err << ", size " << info.width() << "x" << info.height() << " max size "
- << maxTextureSize << " color type " << (int)info.colorType() << " has context "
- << (int)(mRenderThread.getGrContext() != nullptr);
- errorHandler->onError(err.str());
- }
- }
- return true;
- }
- return false;
-}
+ layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
-void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
- GrDirectContext* context = thread.getGrContext();
- if (context && !bitmap->isHardware()) {
- ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
- auto image = bitmap->makeImage();
- if (image.get()) {
- skgpu::ganesh::PinAsTexture(context, image.get());
- skgpu::ganesh::UnpinTexture(context, image.get());
- // A submit is necessary as there may not be a frame coming soon, so without a call
- // to submit these texture uploads can just sit in the queue building up until
- // we run out of RAM
- context->flushAndSubmit();
- }
+ // TODO: put localized light center calculation and storage to a drawable related code.
+ // It does not seem right to store something localized in a global state
+ // fix here and in recordLayers
+ const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
+ Vector3 transformedLightCenter(savedLightCenter);
+ // map current light center into RenderNode's coordinate space
+ layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
+ LightingInfo::setLightCenterRaw(transformedLightCenter);
+
+ const RenderProperties& properties = layerNode->properties();
+ const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+ if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
+ return false;
}
+
+ ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
+ bounds.height());
+
+ layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
+ layerCanvas->clear(SK_ColorTRANSPARENT);
+
+ RenderNodeDrawable root(layerNode, layerCanvas, false);
+ root.forceDraw(layerCanvas);
+ layerCanvas->restoreToCount(saveCount);
+
+ LightingInfo::setLightCenterRaw(savedLightCenter);
+ return true;
}
static void savePictureAsync(const sk_sp<SkData>& data, const std::string& filename) {
@@ -599,45 +487,6 @@
}
}
-void SkiaPipeline::dumpResourceCacheUsage() const {
- int resources;
- size_t bytes;
- mRenderThread.getGrContext()->getResourceCacheUsage(&resources, &bytes);
- size_t maxBytes = mRenderThread.getGrContext()->getResourceCacheLimit();
-
- SkString log("Resource Cache Usage:\n");
- log.appendf("%8d items\n", resources);
- log.appendf("%8zu bytes (%.2f MB) out of %.2f MB maximum\n", bytes,
- bytes * (1.0f / (1024.0f * 1024.0f)), maxBytes * (1.0f / (1024.0f * 1024.0f)));
-
- ALOGD("%s", log.c_str());
-}
-
-void SkiaPipeline::setHardwareBuffer(AHardwareBuffer* buffer) {
- if (mHardwareBuffer) {
- AHardwareBuffer_release(mHardwareBuffer);
- mHardwareBuffer = nullptr;
- }
-
- if (buffer) {
- AHardwareBuffer_acquire(buffer);
- mHardwareBuffer = buffer;
- }
-}
-
-sk_sp<SkSurface> SkiaPipeline::getBufferSkSurface(
- const renderthread::HardwareBufferRenderParams& bufferParams) {
- auto bufferColorSpace = bufferParams.getColorSpace();
- if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
- !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
- mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
- mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
- bufferColorSpace, nullptr, true);
- mBufferColorSpace = bufferColorSpace;
- }
- return mBufferSurface;
-}
-
void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
mColorMode = colorMode;
switch (colorMode) {
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index befee89..cf14b1f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -42,18 +42,9 @@
void onDestroyHardwareResources() override;
- bool pinImages(std::vector<SkImage*>& mutableImages) override;
- bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
- void unpinImages() override;
-
void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
bool opaque, const LightInfo& lightInfo) override;
- // If the given node didn't have a layer surface, or had one of the wrong size, this method
- // creates a new one and returns true. Otherwise does nothing and returns false.
- bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
- ErrorHandler* errorHandler) override;
-
void setSurfaceColorProperties(ColorMode colorMode) override;
SkColorType getSurfaceColorType() const override { return mSurfaceColorType; }
sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; }
@@ -63,9 +54,8 @@
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
const SkMatrix& preTransform);
- static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
-
- void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque);
+ bool renderLayerImpl(RenderNode* layerNode, const Rect& layerDamage);
+ virtual void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) = 0;
// Sets the recording callback to the provided function and the recording mode
// to CallbackAPI
@@ -75,19 +65,11 @@
mCaptureMode = callback ? CaptureMode::CallbackAPI : CaptureMode::None;
}
- virtual void setHardwareBuffer(AHardwareBuffer* buffer) override;
- bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
-
void setTargetSdrHdrRatio(float ratio) override;
protected:
- sk_sp<SkSurface> getBufferSkSurface(
- const renderthread::HardwareBufferRenderParams& bufferParams);
- void dumpResourceCacheUsage() const;
-
renderthread::RenderThread& mRenderThread;
- AHardwareBuffer* mHardwareBuffer = nullptr;
sk_sp<SkSurface> mBufferSurface = nullptr;
sk_sp<SkColorSpace> mBufferColorSpace = nullptr;
@@ -125,8 +107,6 @@
// Set up a multi frame capture.
bool setupMultiFrameCapture();
- std::vector<sk_sp<SkImage>> mPinnedImages;
-
// Block of properties used only for debugging to record a SkPicture and save it in a file.
// There are three possible ways of recording drawing commands.
enum class CaptureMode {
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index fd0a8e0..d06dba0 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "SkiaVulkanPipeline.h"
+#include "pipeline/skia/SkiaVulkanPipeline.h"
#include <GrDirectContext.h>
#include <GrTypes.h>
@@ -28,10 +28,10 @@
#include "DeferredLayerUpdater.h"
#include "LightingInfo.h"
#include "Readback.h"
-#include "ShaderCache.h"
-#include "SkiaPipeline.h"
-#include "SkiaProfileRenderer.h"
-#include "VkInteropFunctorDrawable.h"
+#include "pipeline/skia/ShaderCache.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
+#include "pipeline/skia/SkiaProfileRenderer.h"
+#include "pipeline/skia/VkInteropFunctorDrawable.h"
#include "renderstate/RenderState.h"
#include "renderthread/Frame.h"
#include "renderthread/IRenderPipeline.h"
@@ -42,7 +42,8 @@
namespace uirenderer {
namespace skiapipeline {
-SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {
+SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread)
+ : SkiaGpuPipeline(thread) {
thread.renderState().registerContextCallback(this);
}
diff --git a/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h
new file mode 100644
index 0000000..9159eae
--- /dev/null
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaGpuPipeline.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaPipeline.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaGpuPipeline : public SkiaPipeline {
+public:
+ SkiaGpuPipeline(renderthread::RenderThread& thread);
+ virtual ~SkiaGpuPipeline();
+
+ virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
+
+ // If the given node didn't have a layer surface, or had one of the wrong size, this method
+ // creates a new one and returns true. Otherwise does nothing and returns false.
+ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) override;
+
+ bool pinImages(std::vector<SkImage*>& mutableImages) override;
+ bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+ void unpinImages() override;
+ void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override;
+ void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override;
+ bool hasHardwareBuffer() override { return mHardwareBuffer != nullptr; }
+
+ static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap);
+
+protected:
+ sk_sp<SkSurface> getBufferSkSurface(
+ const renderthread::HardwareBufferRenderParams& bufferParams);
+ void dumpResourceCacheUsage() const;
+
+ AHardwareBuffer* mHardwareBuffer = nullptr;
+
+private:
+ std::vector<sk_sp<SkImage>> mPinnedImages;
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h
similarity index 95%
rename from libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
rename to libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h
index ebe8b6e..6e74782 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaOpenGLPipeline.h
@@ -19,7 +19,7 @@
#include <EGL/egl.h>
#include <system/window.h>
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
#include "renderstate/RenderState.h"
#include "renderthread/HardwareBufferRenderParams.h"
@@ -30,7 +30,7 @@
namespace uirenderer {
namespace skiapipeline {
-class SkiaOpenGLPipeline : public SkiaPipeline, public IGpuContextCallback {
+class SkiaOpenGLPipeline : public SkiaGpuPipeline, public IGpuContextCallback {
public:
SkiaOpenGLPipeline(renderthread::RenderThread& thread);
virtual ~SkiaOpenGLPipeline();
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h
similarity index 95%
rename from libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
rename to libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h
index 624eaa5..0d30df4 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/platform/android/pipeline/skia/SkiaVulkanPipeline.h
@@ -17,7 +17,7 @@
#pragma once
#include "SkRefCnt.h"
-#include "SkiaPipeline.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
#include "renderstate/RenderState.h"
#include "renderthread/HardwareBufferRenderParams.h"
#include "renderthread/VulkanManager.h"
@@ -30,7 +30,7 @@
namespace uirenderer {
namespace skiapipeline {
-class SkiaVulkanPipeline : public SkiaPipeline, public IGpuContextCallback {
+class SkiaVulkanPipeline : public SkiaGpuPipeline, public IGpuContextCallback {
public:
explicit SkiaVulkanPipeline(renderthread::RenderThread& thread);
virtual ~SkiaVulkanPipeline();
diff --git a/libs/hwui/platform/host/android/api-level.h b/libs/hwui/platform/host/android/api-level.h
new file mode 120000
index 0000000..4fb4784
--- /dev/null
+++ b/libs/hwui/platform/host/android/api-level.h
@@ -0,0 +1 @@
+../../../../../../../bionic/libc/include/android/api-level.h
\ No newline at end of file
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h
new file mode 100644
index 0000000..a717265
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaGpuPipeline.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaPipeline.h"
+#include "renderthread/Frame.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaGpuPipeline : public SkiaPipeline {
+public:
+ SkiaGpuPipeline(renderthread::RenderThread& thread) : SkiaPipeline(thread) {}
+ ~SkiaGpuPipeline() {}
+
+ bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; }
+ bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override { return false; }
+ void unpinImages() override {}
+
+ // If the given node didn't have a layer surface, or had one of the wrong size, this method
+ // creates a new one and returns true. Otherwise does nothing and returns false.
+ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
+ ErrorHandler* errorHandler) override {
+ return false;
+ }
+ void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) override {}
+ void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) override {}
+ bool hasHardwareBuffer() override { return false; }
+
+ renderthread::MakeCurrentResult makeCurrent() override {
+ return renderthread::MakeCurrentResult::Failed;
+ }
+ renderthread::Frame getFrame() override { return renderthread::Frame(0, 0, 0); }
+ renderthread::IRenderPipeline::DrawResult draw(
+ const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
+ const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
+ const renderthread::HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) override {
+ return {false, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd(-1)};
+ }
+ bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+ const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+ bool* requireSwap) override {
+ return false;
+ }
+ DeferredLayerUpdater* createTextureLayer() override { return nullptr; }
+ bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override {
+ return false;
+ }
+ [[nodiscard]] android::base::unique_fd flush() override {
+ return android::base::unique_fd(-1);
+ };
+ void onStop() override {}
+ bool isSurfaceReady() override { return false; }
+ bool isContextReady() override { return false; }
+
+ const SkM44& getPixelSnapMatrix() const override {
+ static const SkM44 sSnapMatrix = SkM44();
+ return sSnapMatrix;
+ }
+ static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h
new file mode 100644
index 0000000..4fafbcc
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaOpenGLPipeline.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaOpenGLPipeline : public SkiaGpuPipeline {
+public:
+ SkiaOpenGLPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {}
+
+ static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h
new file mode 100644
index 0000000..d54caef
--- /dev/null
+++ b/libs/hwui/platform/host/pipeline/skia/SkiaVulkanPipeline.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include "pipeline/skia/SkiaGpuPipeline.h"
+
+namespace android {
+
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaVulkanPipeline : public SkiaGpuPipeline {
+public:
+ SkiaVulkanPipeline(renderthread::RenderThread& thread) : SkiaGpuPipeline(thread) {}
+
+ static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor) {}
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 1fbd580..22de2f2 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -35,8 +35,8 @@
#include "Properties.h"
#include "RenderThread.h"
#include "hwui/Canvas.h"
+#include "pipeline/skia/SkiaGpuPipeline.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
-#include "pipeline/skia/SkiaPipeline.h"
#include "pipeline/skia/SkiaVulkanPipeline.h"
#include "thread/CommonPool.h"
#include "utils/GLUtils.h"
@@ -108,7 +108,7 @@
}
void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) {
- skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap);
+ skiapipeline::SkiaGpuPipeline::prepareToDraw(thread, bitmap);
}
CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index b8c3a4d..ee1d1f8 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -30,8 +30,6 @@
#include "SwapBehavior.h"
#include "hwui/Bitmap.h"
-class GrDirectContext;
-
struct ANativeWindow;
namespace android {
@@ -94,7 +92,6 @@
virtual void setSurfaceColorProperties(ColorMode colorMode) = 0;
virtual SkColorType getSurfaceColorType() const = 0;
virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0;
- virtual GrSurfaceOrigin getSurfaceOrigin() = 0;
virtual void setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
diff --git a/media/java/android/media/FadeManagerConfiguration.java b/media/java/android/media/FadeManagerConfiguration.java
index eaafa59..6d84e70 100644
--- a/media/java/android/media/FadeManagerConfiguration.java
+++ b/media/java/android/media/FadeManagerConfiguration.java
@@ -836,7 +836,7 @@
*/
public Builder(@NonNull FadeManagerConfiguration fmc) {
mFadeState = fmc.mFadeState;
- mUsageToFadeWrapperMap = fmc.mUsageToFadeWrapperMap.clone();
+ copyUsageToFadeWrapperMapInternal(fmc.mUsageToFadeWrapperMap);
mAttrToFadeWrapperMap = new ArrayMap<AudioAttributes, FadeVolumeShaperConfigsWrapper>(
fmc.mAttrToFadeWrapperMap);
mFadeableUsages = fmc.mFadeableUsages.clone();
@@ -1459,6 +1459,14 @@
}
}
+ private void copyUsageToFadeWrapperMapInternal(
+ SparseArray<FadeVolumeShaperConfigsWrapper> usageToFadeWrapperMap) {
+ for (int index = 0; index < usageToFadeWrapperMap.size(); index++) {
+ mUsageToFadeWrapperMap.put(usageToFadeWrapperMap.keyAt(index),
+ new FadeVolumeShaperConfigsWrapper(usageToFadeWrapperMap.valueAt(index)));
+ }
+ }
+
private void validateFadeState(int state) {
switch(state) {
case FADE_STATE_DISABLED:
@@ -1551,6 +1559,12 @@
FadeVolumeShaperConfigsWrapper() {}
+ FadeVolumeShaperConfigsWrapper(@NonNull FadeVolumeShaperConfigsWrapper wrapper) {
+ Objects.requireNonNull(wrapper, "Fade volume shaper configs wrapper cannot be null");
+ this.mFadeOutVolShaperConfig = wrapper.mFadeOutVolShaperConfig;
+ this.mFadeInVolShaperConfig = wrapper.mFadeInVolShaperConfig;
+ }
+
public void setFadeOutVolShaperConfig(@Nullable VolumeShaper.Configuration fadeOutConfig) {
mFadeOutVolShaperConfig = fadeOutConfig;
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 3f9440b..5aa006b 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -1309,18 +1309,24 @@
return;
}
- RoutingController newController;
- if (sessionInfo.isSystemSession()) {
- newController = getSystemController();
- newController.setRoutingSessionInfo(sessionInfo);
+ RoutingController newController = addRoutingController(sessionInfo);
+ notifyTransfer(oldController, newController);
+ }
+
+ @NonNull
+ private RoutingController addRoutingController(@NonNull RoutingSessionInfo session) {
+ RoutingController controller;
+ if (session.isSystemSession()) {
+ // mSystemController is never released, so we only need to update its status.
+ mSystemController.setRoutingSessionInfo(session);
+ controller = mSystemController;
} else {
- newController = new RoutingController(sessionInfo);
+ controller = new RoutingController(session);
synchronized (mLock) {
- mNonSystemRoutingControllers.put(newController.getId(), newController);
+ mNonSystemRoutingControllers.put(controller.getId(), controller);
}
}
-
- notifyTransfer(oldController, newController);
+ return controller;
}
void updateControllerOnHandler(RoutingSessionInfo sessionInfo) {
@@ -1329,40 +1335,12 @@
return;
}
- if (sessionInfo.isSystemSession()) {
- // The session info is sent from SystemMediaRoute2Provider.
- RoutingController systemController = getSystemController();
- systemController.setRoutingSessionInfo(sessionInfo);
- notifyControllerUpdated(systemController);
- return;
+ RoutingController controller =
+ getMatchingController(sessionInfo, /* logPrefix */ "updateControllerOnHandler");
+ if (controller != null) {
+ controller.setRoutingSessionInfo(sessionInfo);
+ notifyControllerUpdated(controller);
}
-
- RoutingController matchingController;
- synchronized (mLock) {
- matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
- }
-
- if (matchingController == null) {
- Log.w(
- TAG,
- "updateControllerOnHandler: Matching controller not found. uniqueSessionId="
- + sessionInfo.getId());
- return;
- }
-
- RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
- if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
- Log.w(
- TAG,
- "updateControllerOnHandler: Provider IDs are not matched. old="
- + oldInfo.getProviderId()
- + ", new="
- + sessionInfo.getProviderId());
- return;
- }
-
- matchingController.setRoutingSessionInfo(sessionInfo);
- notifyControllerUpdated(matchingController);
}
void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) {
@@ -1371,34 +1349,47 @@
return;
}
- RoutingController matchingController;
- synchronized (mLock) {
- matchingController = mNonSystemRoutingControllers.get(sessionInfo.getId());
- }
+ RoutingController matchingController =
+ getMatchingController(sessionInfo, /* logPrefix */ "releaseControllerOnHandler");
- if (matchingController == null) {
- if (DEBUG) {
- Log.d(
- TAG,
- "releaseControllerOnHandler: Matching controller not found. "
- + "uniqueSessionId="
- + sessionInfo.getId());
+ if (matchingController != null) {
+ matchingController.releaseInternal(/* shouldReleaseSession= */ false);
+ }
+ }
+
+ @Nullable
+ private RoutingController getMatchingController(
+ RoutingSessionInfo sessionInfo, String logPrefix) {
+ if (sessionInfo.isSystemSession()) {
+ return getSystemController();
+ } else {
+ RoutingController controller;
+ synchronized (mLock) {
+ controller = mNonSystemRoutingControllers.get(sessionInfo.getId());
}
- return;
- }
- RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
- if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
- Log.w(
- TAG,
- "releaseControllerOnHandler: Provider IDs are not matched. old="
- + oldInfo.getProviderId()
- + ", new="
- + sessionInfo.getProviderId());
- return;
- }
+ if (controller == null) {
+ Log.w(
+ TAG,
+ logPrefix
+ + ": Matching controller not found. uniqueSessionId="
+ + sessionInfo.getId());
+ return null;
+ }
- matchingController.releaseInternal(/* shouldReleaseSession= */ false);
+ RoutingSessionInfo oldInfo = controller.getRoutingSessionInfo();
+ if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
+ Log.w(
+ TAG,
+ logPrefix
+ + ": Provider IDs are not matched. old="
+ + oldInfo.getProviderId()
+ + ", new="
+ + sessionInfo.getProviderId());
+ return null;
+ }
+ return controller;
+ }
}
void onRequestCreateControllerByManagerOnHandler(
@@ -3129,20 +3120,8 @@
private void onTransferred(
@NonNull RoutingSessionInfo oldSession, @NonNull RoutingSessionInfo newSession) {
- if (!oldSession.isSystemSession()
- && !TextUtils.equals(
- getClientPackageName(), oldSession.getClientPackageName())) {
- return;
- }
-
- if (!newSession.isSystemSession()
- && !TextUtils.equals(
- getClientPackageName(), newSession.getClientPackageName())) {
- return;
- }
-
- // For successful in-session transfer, onControllerUpdated() handles it.
- if (TextUtils.equals(oldSession.getId(), newSession.getId())) {
+ if (!isSessionRelatedToTargetPackageName(oldSession)
+ || !isSessionRelatedToTargetPackageName(newSession)) {
return;
}
@@ -3169,16 +3148,14 @@
private void onTransferFailed(
@NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) {
- if (!session.isSystemSession()
- && !TextUtils.equals(getClientPackageName(), session.getClientPackageName())) {
+ if (!isSessionRelatedToTargetPackageName(session)) {
return;
}
notifyTransferFailure(route);
}
private void onSessionUpdated(@NonNull RoutingSessionInfo session) {
- if (!session.isSystemSession()
- && !TextUtils.equals(getClientPackageName(), session.getClientPackageName())) {
+ if (!isSessionRelatedToTargetPackageName(session)) {
return;
}
@@ -3193,6 +3170,15 @@
notifyControllerUpdated(controller);
}
+ /**
+ * Returns {@code true} if the session is a system session or if its client package name
+ * matches the proxy router's target package name.
+ */
+ private boolean isSessionRelatedToTargetPackageName(@NonNull RoutingSessionInfo session) {
+ return session.isSystemSession()
+ || TextUtils.equals(getClientPackageName(), session.getClientPackageName());
+ }
+
private void onSessionCreatedOnHandler(
int requestId, @NonNull RoutingSessionInfo sessionInfo) {
MediaRouter2Manager.TransferRequest matchingRequest = null;
@@ -3237,19 +3223,19 @@
}
}
- private void onSessionUpdatedOnHandler(@NonNull RoutingSessionInfo sessionInfo) {
+ private void onSessionUpdatedOnHandler(@NonNull RoutingSessionInfo updatedSession) {
for (MediaRouter2Manager.TransferRequest request : mTransferRequests) {
String sessionId = request.mOldSessionInfo.getId();
- if (!TextUtils.equals(sessionId, sessionInfo.getId())) {
+ if (!TextUtils.equals(sessionId, updatedSession.getId())) {
continue;
}
- if (sessionInfo.getSelectedRoutes().contains(request.mTargetRoute.getId())) {
+
+ if (updatedSession.getSelectedRoutes().contains(request.mTargetRoute.getId())) {
mTransferRequests.remove(request);
- this.onTransferred(request.mOldSessionInfo, sessionInfo);
break;
}
}
- this.onSessionUpdated(sessionInfo);
+ this.onSessionUpdated(updatedSession);
}
private void onSessionReleasedOnHandler(@NonNull RoutingSessionInfo session) {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 50fdb8a..91c4f11 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -25,13 +25,6 @@
}
flag {
- name: "disable_screen_off_broadcast_receiver"
- namespace: "media_solutions"
- description: "Disables the broadcast receiver that prevents scanning when the screen is off."
- bug: "304234628"
-}
-
-flag {
name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling"
namespace: "media_solutions"
description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller."
@@ -108,6 +101,16 @@
}
flag {
+ name: "enable_mr2_service_non_main_bg_thread"
+ namespace: "media_solutions"
+ description: "Enables the use of a background thread in the media routing framework, instead of using the main thread."
+ bug: "310145678"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_screen_off_scanning"
is_exported: true
namespace: "media_solutions"
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
index c48a956..74b5afe 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/FadeManagerConfigurationUnitTest.java
@@ -196,8 +196,7 @@
FadeManagerConfiguration fmcObj = new FadeManagerConfiguration
.Builder(TEST_FADE_OUT_DURATION_MS, TEST_FADE_IN_DURATION_MS).build();
- FadeManagerConfiguration fmc = new FadeManagerConfiguration
- .Builder(fmcObj).build();
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder(fmcObj).build();
expect.withMessage("Fade state for copy builder").that(fmc.getFadeState())
.isEqualTo(fmcObj.getFadeState());
@@ -249,6 +248,45 @@
}
@Test
+ public void build_withCopyConstructor_doesnotChangeOriginal() {
+ FadeManagerConfiguration copyConstructedFmc = new FadeManagerConfiguration.Builder(mFmc)
+ .setFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_OUT_DURATION_MS)
+ .setFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_IN_DURATION_MS)
+ .build();
+
+ expect.withMessage("Fade out duration for media usage of default constructor")
+ .that(mFmc.getFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(DEFAULT_FADE_OUT_DURATION_MS);
+ expect.withMessage("Fade out duration for media usage of default constructor")
+ .that(mFmc.getFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(DEFAULT_FADE_IN_DURATION_MS);
+ expect.withMessage("Fade out duration for media usage of copy constructor")
+ .that(copyConstructedFmc.getFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(TEST_FADE_OUT_DURATION_MS);
+ expect.withMessage("Fade out duration for media usage of copy constructor")
+ .that(copyConstructedFmc.getFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA))
+ .isEqualTo(TEST_FADE_IN_DURATION_MS);
+ }
+
+ @Test
+ public void build_withCopyConstructor_equals() {
+ FadeManagerConfiguration fmc = new FadeManagerConfiguration.Builder()
+ .setFadeableUsages(List.of(AudioAttributes.USAGE_MEDIA,
+ AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+ AudioAttributes.USAGE_ASSISTANT,
+ AudioAttributes.USAGE_EMERGENCY))
+ .setFadeOutDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_OUT_DURATION_MS)
+ .setFadeInDurationForUsage(AudioAttributes.USAGE_MEDIA, TEST_FADE_IN_DURATION_MS)
+ .build();
+
+ FadeManagerConfiguration copyConstructedFmc =
+ new FadeManagerConfiguration.Builder(fmc).build();
+
+ expect.withMessage("Fade manager config constructed using copy constructor").that(fmc)
+ .isEqualTo(copyConstructedFmc);
+ }
+
+ @Test
public void testGetDefaultFadeOutDuration() {
expect.withMessage("Default fade out duration")
.that(FadeManagerConfiguration.getDefaultFadeOutDurationMillis())
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 8227bdb..882afca 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -41,16 +41,15 @@
using namespace std::chrono_literals;
-using HalSessionHint = aidl::android::hardware::power::SessionHint;
-using HalSessionMode = aidl::android::hardware::power::SessionMode;
-using HalWorkDuration = aidl::android::hardware::power::WorkDuration;
+// Namespace for AIDL types coming from the PowerHAL
+namespace hal = aidl::android::hardware::power;
using android::base::StringPrintf;
struct APerformanceHintSession;
constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
-struct AWorkDuration : public HalWorkDuration {};
+struct AWorkDuration : public hal::WorkDuration {};
struct APerformanceHintManager {
public:
@@ -115,7 +114,7 @@
// Last hint reported from sendHint indexed by hint value
std::vector<int64_t> mLastHintSentTimestamp;
// Cached samples
- std::vector<HalWorkDuration> mActualWorkDurations;
+ std::vector<hal::WorkDuration> mActualWorkDurations;
std::string mSessionName;
static int32_t sIDCounter;
// The most recent set of thread IDs
@@ -207,8 +206,9 @@
mTargetDurationNanos(targetDurationNanos),
mFirstTargetMetTimestamp(0),
mLastTargetMetTimestamp(0) {
- const std::vector<HalSessionHint> sessionHintRange{ndk::enum_range<HalSessionHint>().begin(),
- ndk::enum_range<HalSessionHint>().end()};
+ const std::vector<hal::SessionHint> sessionHintRange{ndk::enum_range<hal::SessionHint>()
+ .begin(),
+ ndk::enum_range<hal::SessionHint>().end()};
mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
mSessionName = android::base::StringPrintf("ADPF Session %" PRId32, ++sIDCounter);
@@ -246,10 +246,10 @@
}
int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNanos) {
- HalWorkDuration workDuration{.durationNanos = actualDurationNanos,
- .workPeriodStartTimestampNanos = 0,
- .cpuDurationNanos = actualDurationNanos,
- .gpuDurationNanos = 0};
+ hal::WorkDuration workDuration{.durationNanos = actualDurationNanos,
+ .workPeriodStartTimestampNanos = 0,
+ .cpuDurationNanos = actualDurationNanos,
+ .gpuDurationNanos = 0};
return reportActualWorkDurationInternal(static_cast<AWorkDuration*>(&workDuration));
}
@@ -323,7 +323,8 @@
int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) {
ndk::ScopedAStatus ret =
- mHintSession->setMode(static_cast<int32_t>(HalSessionMode::POWER_EFFICIENCY), enabled);
+ mHintSession->setMode(static_cast<int32_t>(hal::SessionMode::POWER_EFFICIENCY),
+ enabled);
if (!ret.isOk()) {
ALOGE("%s: HintSession setPreferPowerEfficiency failed: %s", __FUNCTION__,
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index 6f20adf..8891b50 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -39,13 +39,13 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.BackgroundThread;
import android.util.LongArrayQueue;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
diff --git a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
new file mode 100644
index 0000000..a6ae68f
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
@@ -0,0 +1,103 @@
+/*
+ * * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Thread for asynchronous event processing. This thread is configured as
+ * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU
+ * resources will be dedicated to it, and it will "have less chance of impacting
+ * the responsiveness of the user interface."
+ * <p>
+ * This thread is best suited for tasks that the user is not actively waiting
+ * for, or for tasks that the user expects to be executed eventually.
+ *
+ * @see com.android.internal.os.BackgroundThread
+ *
+ * TODO: b/326916057 depend on modules-utils-backgroundthread instead
+ * @hide
+ */
+public final class BackgroundThread extends HandlerThread {
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static BackgroundThread sInstance;
+ @GuardedBy("sLock")
+ private static Handler sHandler;
+ @GuardedBy("sLock")
+ private static HandlerExecutor sHandlerExecutor;
+
+ private BackgroundThread() {
+ super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ }
+
+ @GuardedBy("sLock")
+ private static void ensureThreadLocked() {
+ if (sInstance == null) {
+ sInstance = new BackgroundThread();
+ sInstance.start();
+ sHandler = new Handler(sInstance.getLooper());
+ sHandlerExecutor = new HandlerExecutor(sHandler);
+ }
+ }
+
+ /**
+ * Get the singleton instance of this class.
+ *
+ * @return the singleton instance of this class
+ */
+ @NonNull
+ public static BackgroundThread get() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sInstance;
+ }
+ }
+
+ /**
+ * Get the singleton {@link Handler} for this class.
+ *
+ * @return the singleton {@link Handler} for this class.
+ */
+ @NonNull
+ public static Handler getHandler() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sHandler;
+ }
+ }
+
+ /**
+ * Get the singleton {@link Executor} for this class.
+ *
+ * @return the singleton {@link Executor} for this class.
+ */
+ @NonNull
+ public static Executor getExecutor() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sHandlerExecutor;
+ }
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
new file mode 100644
index 0000000..948ebcca
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * An adapter {@link Executor} that posts all executed tasks onto the given
+ * {@link Handler}.
+ *
+ * TODO: b/326916057 depend on modules-utils-backgroundthread instead
+ * @hide
+ */
+public class HandlerExecutor implements Executor {
+ private final Handler mHandler;
+
+ public HandlerExecutor(@NonNull Handler handler) {
+ mHandler = Objects.requireNonNull(handler);
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RejectedExecutionException(mHandler + " is shutting down");
+ }
+ }
+}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index bc35a85..9fd386f 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -68,6 +68,13 @@
<string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
<!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
<string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
+ <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create passkey flow. [CHAR LIMIT=200] -->
+ <string name="choose_create_single_tap_passkey_title">Use your screen lock to create a passkey for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
+ <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create password flow. [CHAR LIMIT=200] -->
+ <string name="choose_create_single_tap_password_title">Use your screen lock to create a password for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
+ <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create flow when the credential type is others. [CHAR LIMIT=200] -->
+ <!-- TODO(b/326243891) : Confirm with team on dynamically setting this based on recent product and ux discussions (does not disrupt e2e) -->
+ <string name="choose_create_single_tap_sign_in_title">Use your screen lock to save sign in info for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
<!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
<string name="passkey">passkey</string>
<string name="password">password</string>
diff --git a/packages/CredentialManager/shared/project.config b/packages/CredentialManager/shared/project.config
new file mode 100644
index 0000000..f748d6c
--- /dev/null
+++ b/packages/CredentialManager/shared/project.config
@@ -0,0 +1,9 @@
+[notify "team"]
+ header = cc
+ email = sgjerry@google.com
+ email = helenqin@google.com
+ email = hemnani@google.com
+ email = shuanghao@google.com
+ email = harinirajan@google.com
+ type = new_changes
+ type = submitted_changes
\ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
index f2c252e..b408c15 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -51,6 +51,8 @@
import com.android.credentialmanager.model.BiometricRequestInfo
import com.android.credentialmanager.model.EntryInfo
+const val CREDENTIAL_ENTRY_PREFIX = "androidx.credentials.provider.credentialEntry."
+
fun EntryInfo.getIntentSenderRequest(
isAutoSelected: Boolean = false
): IntentSenderRequest? {
@@ -140,7 +142,8 @@
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it),
+ biometricRequest = predetermineAndValidateBiometricFlow(it,
+ CREDENTIAL_ENTRY_PREFIX),
)
)
}
@@ -169,7 +172,8 @@
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it),
+ biometricRequest = predetermineAndValidateBiometricFlow(it,
+ CREDENTIAL_ENTRY_PREFIX),
)
)
}
@@ -197,7 +201,8 @@
isDefaultIconPreferredAsSingleProvider =
credentialEntry.isDefaultIconPreferredAsSingleProvider,
affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
- biometricRequest = predetermineAndValidateBiometricFlow(it),
+ biometricRequest = predetermineAndValidateBiometricFlow(it,
+ CREDENTIAL_ENTRY_PREFIX),
)
)
}
@@ -217,21 +222,26 @@
* Note that the required values, such as the provider info's icon or display name, or the entries
* credential type or userName, and finally the display info's app name, are non-null and must
* exist to run through the flow.
+ *
+ * @param hintPrefix a string prefix indicating the type of entry being utilized, since both create
+ * and get flows utilize slice params; includes the final '.' before the name of the type (e.g.
+ * androidx.credentials.provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS must have
+ * 'hintPrefix' up to "androidx.credentials.provider.credentialEntry.")
* // TODO(b/326243754) : Presently, due to dependencies, the opId bit is parsed but is never
* // expected to be used. When it is added, it should be lightly validated.
*/
-private fun predetermineAndValidateBiometricFlow(
- it: Entry
+fun predetermineAndValidateBiometricFlow(
+ entry: Entry,
+ hintPrefix: String,
): BiometricRequestInfo? {
// TODO(b/326243754) : When available, use the official jetpack structured type
- val allowedAuthenticators: Int? = it.slice.items.firstOrNull {
- it.hasHint("androidx.credentials." +
- "provider.credentialEntry.SLICE_HINT_ALLOWED_AUTHENTICATORS")
+ val allowedAuthenticators: Int? = entry.slice.items.firstOrNull {
+ it.hasHint(hintPrefix + "SLICE_HINT_ALLOWED_AUTHENTICATORS")
}?.int
// This is optional and does not affect validating the biometric flow in any case
- val opId: Int? = it.slice.items.firstOrNull {
- it.hasHint("androidx.credentials.provider.credentialEntry.SLICE_HINT_CRYPTO_OP_ID")
+ val opId: Int? = entry.slice.items.firstOrNull {
+ it.hasHint(hintPrefix + "SLICE_HINT_CRYPTO_OP_ID")
}?.int
if (allowedAuthenticators != null) {
return BiometricRequestInfo(opId = opId, allowedAuthenticators = allowedAuthenticators)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 28c4047..a039753 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -18,6 +18,7 @@
import android.app.Activity
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResult
import android.os.IBinder
import android.text.TextUtils
import android.util.Log
@@ -39,6 +40,7 @@
import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.findBiometricFlowEntry
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
import com.android.credentialmanager.logging.LifecycleEvent
@@ -304,7 +306,11 @@
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
currentScreenState =
- if (uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
+ // An autoselect flow never makes it to the more options screen
+ if (findBiometricFlowEntry(activeEntry = activeEntry,
+ isAutoSelectFlow = false) != null) CreateScreenState.BIOMETRIC_SELECTION
+ else if (
+ uiState.createCredentialUiState?.requestDisplayInfo?.userSetDefaultProviderIds
?.contains(activeEntry.activeProvider.id) ?: true ||
!(uiState.createCredentialUiState?.foundCandidateFromUserDefaultProvider
?: false) ||
@@ -330,7 +336,10 @@
)
}
- fun createFlowOnEntrySelected(selectedEntry: EntryInfo) {
+ fun createFlowOnEntrySelected(
+ selectedEntry: EntryInfo,
+ authResult: AuthenticationResult? = null
+ ) {
val providerId = selectedEntry.providerId
val entryKey = selectedEntry.entryKey
val entrySubkey = selectedEntry.entrySubkey
@@ -341,6 +350,9 @@
uiState = uiState.copy(
selectedEntry = selectedEntry,
providerActivityState = ProviderActivityState.READY_TO_LAUNCH,
+ biometricState = if (authResult == null) uiState.biometricState else uiState
+ .biometricState.copy(biometricResult = BiometricResult(
+ biometricAuthenticationResult = authResult))
)
} else {
credManRepo.onOptionSelected(
@@ -367,9 +379,4 @@
fun logUiEvent(uiEventEnum: UiEventEnum) {
this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName)
}
-
- companion object {
- // TODO(b/326243754) : Replace/remove once all failure flows added in
- const val TEMPORARY_FAILURE_CODE = Integer.MIN_VALUE
- }
}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index fd6fc6a..358ebfa 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -52,10 +52,11 @@
import androidx.credentials.provider.RemoteEntry
import org.json.JSONObject
import android.credentials.flags.Flags
+import com.android.credentialmanager.createflow.isBiometricFlow
import com.android.credentialmanager.getflow.TopBrandingContent
+import com.android.credentialmanager.ktx.predetermineAndValidateBiometricFlow
import java.time.Instant
-
fun getAppLabel(
pm: PackageManager,
appPackageName: String
@@ -237,6 +238,9 @@
class CreateFlowUtils {
companion object {
+
+ private const val CREATE_ENTRY_PREFIX = "androidx.credentials.provider.createEntry."
+
/**
* Note: caller required handle empty list due to parsing error.
*/
@@ -417,12 +421,21 @@
}
}
val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
+ val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
+ compareByDescending { it.first.lastUsedTime }
+ )
+ val activeEntry = toActiveEntry(
+ defaultProvider = defaultProvider,
+ sortedCreateOptionsPairs = sortedCreateOptionsPairs,
+ remoteEntry = remoteEntry,
+ remoteEntryProvider = remoteEntryProvider,
+ )
+ val isBiometricFlow = if (activeEntry == null) false else isBiometricFlow(activeEntry,
+ sortedCreateOptionsPairs, requestDisplayInfo)
val initialScreenState = toCreateScreenState(
createOptionSize = createOptionsPairs.size,
remoteEntry = remoteEntry,
- )
- val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
- compareByDescending { it.first.lastUsedTime }
+ isBiometricFlow = isBiometricFlow
)
return CreateCredentialUiState(
enabledProviders = enabledProviders,
@@ -430,12 +443,7 @@
currentScreenState = initialScreenState,
requestDisplayInfo = requestDisplayInfo,
sortedCreateOptionsPairs = sortedCreateOptionsPairs,
- activeEntry = toActiveEntry(
- defaultProvider = defaultProvider,
- sortedCreateOptionsPairs = sortedCreateOptionsPairs,
- remoteEntry = remoteEntry,
- remoteEntryProvider = remoteEntryProvider,
- ),
+ activeEntry = activeEntry,
remoteEntry = remoteEntry,
foundCandidateFromUserDefaultProvider = defaultProviderSetByUser != null,
)
@@ -444,9 +452,12 @@
fun toCreateScreenState(
createOptionSize: Int,
remoteEntry: RemoteInfo?,
+ isBiometricFlow: Boolean,
): CreateScreenState {
return if (createOptionSize == 0 && remoteEntry != null) {
CreateScreenState.EXTERNAL_ONLY_SELECTION
+ } else if (isBiometricFlow) {
+ CreateScreenState.BIOMETRIC_SELECTION
} else {
CreateScreenState.CREATION_OPTION_SELECTION
}
@@ -503,8 +514,8 @@
it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" +
"SELECT_ALLOWED")
}?.text == "true",
- // TODO(b/326243754) : Handle this when the create flow is added; for now the
- // create flow does not support biometric values
+ biometricRequest = predetermineAndValidateBiometricFlow(it,
+ CREATE_ENTRY_PREFIX),
)
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index a30956e..fa17735 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -26,11 +26,14 @@
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.getCreateTitleResCode
import com.android.credentialmanager.getflow.ProviderDisplayInfo
import com.android.credentialmanager.getflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.generateDisplayTitleTextResCode
import com.android.credentialmanager.model.BiometricRequestInfo
+import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.model.get.ProviderInfo
import java.lang.Exception
@@ -39,14 +42,30 @@
* Aggregates common display information used for the Biometric Flow.
* Namely, this adds the ability to encapsulate the [providerIcon], the providers icon, the
* [providerName], which represents the name of the provider, the [displayTitleText] which is
- * the large text displaying the flow in progress, and the [descriptionAboveBiometricButton], which
+ * the large text displaying the flow in progress, and the [descriptionForCredential], which
* describes details of where the credential is being saved, and how.
+ * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with Your@Email.com:
+ *
+ * 'get' flow:
+ * - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
+ * - [displayTitleText] = "Use your saved passkey for Any Provider?"
+ * - [descriptionForCredential] = "Use your screen lock to sign in to Any Provider with
+ * Your@Email.com"
+ *
+ * 'create' flow:
+ * - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
+ * - [displayTitleText] = "Create passkey to sign in to Any Provider?"
+ * - [descriptionForCredential] = "Use your screen lock to create a passkey for Any Provider?"
+ * ).
+ *
+ * The above are examples; the credential type can change depending on scenario.
+ * // TODO(b/326243891) : Finalize once all the strings and create flow is iterated to completion
*/
data class BiometricDisplayInfo(
val providerIcon: Bitmap,
val providerName: String,
val displayTitleText: String,
- val descriptionAboveBiometricButton: String,
+ val descriptionForCredential: String,
val biometricRequestInfo: BiometricRequestInfo,
)
@@ -56,10 +75,7 @@
* additional states that may improve the flow.
*/
data class BiometricState(
- val biometricResult: BiometricResult? = null,
- val biometricError: BiometricError? = null,
- val biometricHelp: BiometricHelp? = null,
- val biometricAcquireInfo: Int? = null,
+ val biometricResult: BiometricResult? = null
)
/**
@@ -108,18 +124,20 @@
.RequestDisplayInfo? = null,
createProviderInfo: EnabledProviderInfo? = null,
) {
+ // TODO(b/330396089) : Add rotation configuration fix with state machine
var biometricDisplayInfo: BiometricDisplayInfo? = null
+ var flowType = FlowType.GET
if (getRequestDisplayInfo != null) {
biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo(getRequestDisplayInfo,
getProviderInfoList,
getProviderDisplayInfo,
context, biometricEntry)
} else if (createRequestDisplayInfo != null) {
- // TODO(b/326243754) : Create Flow to be implemented in follow up
- biometricDisplayInfo = validateBiometricCreateFlow(
+ flowType = FlowType.CREATE
+ biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo(
createRequestDisplayInfo,
- createProviderInfo
- )
+ createProviderInfo,
+ context, biometricEntry)
}
if (biometricDisplayInfo == null) {
@@ -128,7 +146,7 @@
}
val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage,
- biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators)
+ biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators, flowType)
val callback: BiometricPrompt.AuthenticationCallback =
setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry,
@@ -154,23 +172,21 @@
/**
* Sets up the biometric prompt with the UI specific bits.
* // TODO(b/326243754) : Pass in opId once dependency is confirmed via CryptoObject
- * // TODO(b/326243754) : Given fallbacks aren't allowed, for now we validate that device creds
- * // are NOT allowed to be passed in to avoid throwing an error. Later, however, once target
- * // alignments occur, we should add the bit back properly.
*/
private fun setupBiometricPrompt(
context: Context,
biometricDisplayInfo: BiometricDisplayInfo,
openMoreOptionsPage: () -> Unit,
requestAllowedAuthenticators: Int,
+ flowType: FlowType,
): BiometricPrompt {
val finalAuthenticators = removeDeviceCredential(requestAllowedAuthenticators)
val biometricPrompt = BiometricPrompt.Builder(context)
.setTitle(biometricDisplayInfo.displayTitleText)
// TODO(b/326243754) : Migrate to using new methods recently aligned upon
- .setNegativeButton(context.getString(R.string
- .dropdown_presentation_more_sign_in_options_text),
+ .setNegativeButton(context.getString(if (flowType == FlowType.GET) R.string
+ .dropdown_presentation_more_sign_in_options_text else R.string.string_more_options),
getMainExecutor(context)) { _, _ ->
openMoreOptionsPage()
}
@@ -178,7 +194,7 @@
.setConfirmationRequired(true)
.setLogoBitmap(biometricDisplayInfo.providerIcon)
.setLogoDescription(biometricDisplayInfo.providerName)
- .setDescription(biometricDisplayInfo.descriptionAboveBiometricButton)
+ .setDescription(biometricDisplayInfo.descriptionForCredential)
.build()
return biometricPrompt
@@ -294,14 +310,16 @@
* checking between the two. The reason for this method matches the logic for the
* [validateBiometricGetFlow] with the only difference being that this is for the create flow.
*/
-private fun validateBiometricCreateFlow(
+private fun validateAndRetrieveBiometricCreateDisplayInfo(
createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo?,
createProviderInfo: EnabledProviderInfo?,
+ context: Context,
+ selectedEntry: EntryInfo,
): BiometricDisplayInfo? {
if (createRequestDisplayInfo != null && createProviderInfo != null) {
- } else if (createRequestDisplayInfo != null && createProviderInfo != null) {
- // TODO(b/326243754) : Create Flow to be implemented in follow up
- return createFlowDisplayValues()
+ if (selectedEntry !is CreateOptionInfo) { return null }
+ return createBiometricDisplayValues(createRequestDisplayInfo, createProviderInfo, context,
+ selectedEntry)
}
return null
}
@@ -346,17 +364,47 @@
username
)
return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
- displayTitleText = displayTitleText, descriptionAboveBiometricButton = descriptionText,
+ displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
}
/**
- * Handles the biometric sign in via the 'create credentials' flow, or early validates this flow
- * needs to fallback.
+ * Handles the biometric sign in via the create credentials flow. Stricter in the get flow in that
+ * if this is called, a result is guaranteed. Specifically, this is guaranteed to return a non-null
+ * value unlike the get counterpart.
*/
-private fun createFlowDisplayValues(): BiometricDisplayInfo? {
- // TODO(b/326243754) : Create Flow to be implemented in follow up
- return null
+private fun createBiometricDisplayValues(
+ createRequestDisplayInfo: com.android.credentialmanager.createflow.RequestDisplayInfo,
+ createProviderInfo: EnabledProviderInfo,
+ context: Context,
+ selectedEntry: CreateOptionInfo,
+): BiometricDisplayInfo {
+ val icon: Bitmap?
+ val providerName: String?
+ val displayTitleText: String?
+ icon = createProviderInfo.icon.toBitmap()
+ providerName = createProviderInfo.displayName
+ displayTitleText = context.getString(
+ getCreateTitleResCode(createRequestDisplayInfo),
+ createRequestDisplayInfo.appName
+ )
+ val descriptionText: String = context.getString(
+ when (createRequestDisplayInfo.type) {
+ CredentialType.PASSKEY ->
+ R.string.choose_create_single_tap_passkey_title
+
+ CredentialType.PASSWORD ->
+ R.string.choose_create_single_tap_password_title
+
+ CredentialType.UNKNOWN ->
+ R.string.choose_create_single_tap_sign_in_title
+ },
+ createRequestDisplayInfo.appName,
+ )
+ // TODO(b/327620327) : Add a subtitle and any other recently aligned ideas
+ return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
+ displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
+ biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt
index 5d2b0ad2..f6140f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/FlowType.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.shared.model
+package com.android.credentialmanager.common
-/** An offset of view, used to adjust bounds. */
-data class ViewPosition(val left: Int = 0, val top: Int = 0)
+enum class FlowType {
+ GET,
+ CREATE
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index af78573..25fb477 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -17,6 +17,7 @@
package com.android.credentialmanager.createflow
import android.credentials.flags.Flags.selectorUiImprovementsEnabled
+import android.hardware.biometrics.BiometricPrompt
import android.text.TextUtils
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
@@ -26,7 +27,6 @@
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.Divider
import androidx.compose.material.icons.Icons
@@ -38,6 +38,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -49,6 +50,7 @@
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
+import com.android.credentialmanager.common.runBiometricFlow
import com.android.credentialmanager.common.ui.ActionButton
import com.android.credentialmanager.common.ui.BodyMediumText
import com.android.credentialmanager.common.ui.BodySmallText
@@ -95,6 +97,22 @@
viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
onLog = { viewModel.logUiEvent(it) },
)
+ CreateScreenState.BIOMETRIC_SELECTION ->
+ BiometricSelectionPage(
+ biometricEntry = createCredentialUiState
+ .activeEntry?.activeEntryInfo,
+ onCancelFlowAndFinish = viewModel::onUserCancel,
+ onIllegalScreenStateAndFinish = viewModel::onIllegalUiState,
+ onMoreOptionSelected =
+ viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderInfo = createCredentialUiState
+ .activeEntry?.activeProvider!!,
+ onBiometricEntrySelected =
+ viewModel::createFlowOnEntrySelected,
+ fallbackToOriginalFlow =
+ viewModel::getFlowOnBackToPrimarySelectionScreen,
+ )
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderList = createCredentialUiState.enabledProviders,
@@ -313,20 +331,9 @@
item { Divider(thickness = 16.dp, color = Color.Transparent) }
item {
HeadlineText(
- text = when (requestDisplayInfo.type) {
- CredentialType.PASSKEY -> stringResource(
- R.string.choose_create_option_passkey_title,
- requestDisplayInfo.appName
- )
- CredentialType.PASSWORD -> stringResource(
- R.string.choose_create_option_password_title,
- requestDisplayInfo.appName
- )
- CredentialType.UNKNOWN -> stringResource(
- R.string.choose_create_option_sign_in_title,
- requestDisplayInfo.appName
- )
- }
+ text = stringResource(
+ getCreateTitleResCode(requestDisplayInfo),
+ requestDisplayInfo.appName)
)
}
item { Divider(thickness = 24.dp, color = Color.Transparent) }
@@ -560,4 +567,32 @@
iconImageVector = Icons.Outlined.QrCodeScanner,
entryHeadlineText = stringResource(R.string.another_device),
)
-}
\ No newline at end of file
+}
+
+@Composable
+internal fun BiometricSelectionPage(
+ biometricEntry: EntryInfo?,
+ onMoreOptionSelected: () -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderInfo: EnabledProviderInfo,
+ onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ onCancelFlowAndFinish: () -> Unit,
+ onIllegalScreenStateAndFinish: (String) -> Unit,
+ fallbackToOriginalFlow: () -> Unit,
+) {
+ if (biometricEntry == null) {
+ fallbackToOriginalFlow()
+ return
+ }
+ runBiometricFlow(
+ biometricEntry = biometricEntry,
+ context = LocalContext.current,
+ openMoreOptionsPage = onMoreOptionSelected,
+ sendDataToProvider = onBiometricEntrySelected,
+ onCancelFlowAndFinish = onCancelFlowAndFinish,
+ createRequestDisplayInfo = requestDisplayInfo,
+ createProviderInfo = enabledProviderInfo,
+ onBiometricFailureFallback = fallbackToOriginalFlow,
+ onIllegalStateAndFinish = onIllegalScreenStateAndFinish,
+ )
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 617a981..1d262ba 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -16,9 +16,11 @@
package com.android.credentialmanager.createflow
+import android.credentials.flags.Flags.credmanBiometricApiEnabled
import android.graphics.drawable.Drawable
-import com.android.credentialmanager.model.EntryInfo
+import com.android.credentialmanager.R
import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.creation.CreateOptionInfo
import com.android.credentialmanager.model.creation.RemoteInfo
@@ -33,14 +35,99 @@
val foundCandidateFromUserDefaultProvider: Boolean,
)
+/**
+ * Checks if this create flow is a biometric flow. Note that this flow differs slightly from the
+ * autoselect 'get' flow. Namely, given there can be multiple providers, rather than multiple
+ * accounts, the idea is that autoselect is ever only enabled for a single provider (or even, in
+ * that case, a single 'type' (family only, or work only) for a provider). However, for all other
+ * cases, the biometric screen should always show up if that entry contains the biometric bit.
+ */
+internal fun findBiometricFlowEntry(
+ activeEntry: ActiveEntry,
+ isAutoSelectFlow: Boolean,
+): CreateOptionInfo? {
+ if (!credmanBiometricApiEnabled()) {
+ return null
+ }
+ if (isAutoSelectFlow) {
+ // Since this is the create flow, auto select will only ever be true for a single provider.
+ // However, for all other cases, biometric should be used if that bit is opted into. If
+ // they clash, autoselect is always preferred, but that's only if there's a single provider.
+ return null
+ }
+ val biometricEntry = getCreateEntry(activeEntry)
+ return if (biometricEntry?.biometricRequest != null) biometricEntry else null
+}
+
+/**
+ * Retrieves the activeEntry by validating it is a [CreateOptionInfo]. This is done by ensuring
+ * that the [activeEntry] exists as a [CreateOptionInfo] to retrieve its [EntryInfo].
+ */
+internal fun getCreateEntry(
+ activeEntry: ActiveEntry?,
+): CreateOptionInfo? {
+ val entry = activeEntry?.activeEntryInfo
+ if (entry !is CreateOptionInfo) {
+ return null
+ }
+ return entry
+}
+
+/**
+* Determines if the flow is a biometric flow by taking into account autoselect criteria.
+*/
+internal fun isBiometricFlow(
+ activeEntry: ActiveEntry,
+ sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
+ requestDisplayInfo: RequestDisplayInfo,
+) = findBiometricFlowEntry(activeEntry, isFlowAutoSelectable(
+ requestDisplayInfo = requestDisplayInfo,
+ activeEntry = activeEntry,
+ sortedCreateOptionsPairs = sortedCreateOptionsPairs
+)) != null
+
+/**
+ * This utility presents the correct resource string for the create flows title conditionally.
+ * Similar to generateDisplayTitleTextResCode in the 'get' flow, but for the create flow instead.
+ * This is for the title, and is a shared resource, unlike the specific unlock request text.
+ * E.g. this will look something like: "Create passkey to sign in to Tribank."
+ * // TODO(b/330396140) : Validate approach and add dynamic auth strings
+ */
+internal fun getCreateTitleResCode(createRequestDisplayInfo: RequestDisplayInfo): Int =
+ when (createRequestDisplayInfo.type) {
+ CredentialType.PASSKEY ->
+ R.string.choose_create_option_passkey_title
+
+ CredentialType.PASSWORD ->
+ R.string.choose_create_option_password_title
+
+ CredentialType.UNKNOWN ->
+ R.string.choose_create_option_sign_in_title
+ }
+
internal fun isFlowAutoSelectable(
uiState: CreateCredentialUiState
): Boolean {
- return uiState.requestDisplayInfo.isAutoSelectRequest &&
- uiState.sortedCreateOptionsPairs.size == 1 &&
- uiState.activeEntry?.activeEntryInfo?.let {
- it is CreateOptionInfo && it.allowAutoSelect
- } ?: false
+ return isFlowAutoSelectable(uiState.requestDisplayInfo, uiState.activeEntry,
+ uiState.sortedCreateOptionsPairs)
+}
+
+/**
+ * When initializing, the [CreateCredentialUiState] is generated after the initial screen is set.
+ * This overloaded method allows identifying if the flow is auto selectable prior to the creation
+ * of the [CreateCredentialUiState].
+ */
+internal fun isFlowAutoSelectable(
+ requestDisplayInfo: RequestDisplayInfo,
+ activeEntry: ActiveEntry?,
+ sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>
+): Boolean {
+ val isAutoSelectRequest = requestDisplayInfo.isAutoSelectRequest
+ if (sortedCreateOptionsPairs.size != 1) {
+ return false
+ }
+ val singleEntry = getCreateEntry(activeEntry)
+ return isAutoSelectRequest && singleEntry?.allowAutoSelect == true
}
internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
@@ -95,6 +182,7 @@
/** The name of the current screen. */
enum class CreateScreenState {
CREATION_OPTION_SELECTION,
+ BIOMETRIC_SELECTION,
MORE_OPTIONS_SELECTION,
DEFAULT_PROVIDER_CONFIRMATION,
EXTERNAL_ONLY_SELECTION,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 4d7272c..6d1a3dd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -144,8 +144,6 @@
} else if (credmanBiometricApiEnabled() && getCredentialUiState
.currentScreenState == GetScreenState.BIOMETRIC_SELECTION) {
BiometricSelectionPage(
- // TODO(b/326243754) : Utilize expected entry for this flow, confirm
- // activeEntry will always be what represents the single tap flow
biometricEntry = getCredentialUiState.activeEntry,
onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
onCancelFlowAndFinish = viewModel::onUserCancel,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 6d5b52a..ac776af 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -238,6 +238,7 @@
/**
* This generates the res code for the large display title text for the selector. For example, it
* retrieves the resource for strings like: "Use your saved passkey for *rpName*".
+ * TODO(b/330396140) : Validate approach and add dynamic auth strings
*/
internal fun generateDisplayTitleTextResCode(
singleEntryType: CredentialType,
diff --git a/packages/EasterEgg/Android.bp b/packages/EasterEgg/Android.bp
index 6f4f9ca..ec1fe39 100644
--- a/packages/EasterEgg/Android.bp
+++ b/packages/EasterEgg/Android.bp
@@ -83,6 +83,7 @@
aconfig_declarations {
name: "easter_egg_flags",
package: "com.android.egg.flags",
+ container: "system",
srcs: [
"easter_egg_flags.aconfig",
],
diff --git a/packages/EasterEgg/easter_egg_flags.aconfig b/packages/EasterEgg/easter_egg_flags.aconfig
index 3268a4f..7ddc238 100644
--- a/packages/EasterEgg/easter_egg_flags.aconfig
+++ b/packages/EasterEgg/easter_egg_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.egg.flags"
+container: "system"
flag {
name: "flag_flag"
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
index 636f98d..4f4fb1b 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
@@ -35,85 +35,97 @@
}
key 1 {
- label: '1'
+ label: '&'
base: '&'
- shift: '1'
+ shift, capslock: '1'
+ shift+capslock: '&'
}
key 2 {
- label: '2'
+ label: '\u00e9'
base: '\u00e9'
- shift: '2'
+ shift, capslock: '2'
+ shift+capslock: '\u00e9'
ralt: '\u0303'
}
key 3 {
- label: '3'
+ label: '"'
base: '"'
- shift: '3'
+ shift, capslock: '3'
+ shift+capslock: '"'
ralt: '#'
}
key 4 {
- label: '4'
+ label: '\''
base: '\''
- shift: '4'
+ shift, capslock: '4'
+ shift+capslock: '\''
ralt: '{'
}
key 5 {
- label: '5'
+ label: '('
base: '('
- shift: '5'
+ shift, capslock: '5'
+ shift+capslock: '('
ralt: '['
}
key 6 {
- label: '6'
+ label: '-'
base: '-'
- shift: '6'
+ shift, capslock: '6'
+ shift+capslock: '-'
ralt: '|'
}
key 7 {
- label: '7'
+ label: '\u00e8'
base: '\u00e8'
- shift: '7'
+ shift, capslock: '7'
+ shift+capslock: '\u00e8'
ralt: '\u0300'
}
key 8 {
- label: '8'
+ label: '_'
base: '_'
- shift: '8'
+ shift, capslock: '8'
+ shift+capslock: '_'
ralt: '\\'
}
key 9 {
- label: '9'
+ label: '\u00e7'
base: '\u00e7'
- shift: '9'
+ shift, capslock: '9'
+ shift+capslock: '\u00e7'
ralt: '^'
}
key 0 {
- label: '0'
+ label: '\u00e0'
base: '\u00e0'
- shift: '0'
+ shift, capslock: '0'
+ shift+capslock: '\u00e0'
ralt: '@'
}
key MINUS {
label: ')'
base: ')'
- shift: '\u00b0'
+ shift, capslock: '\u00b0'
+ shift+capslock: ')'
ralt: ']'
}
key EQUALS {
label: '='
base: '='
- shift: '+'
+ shift, capslock: '+'
+ shift+capslock: '='
ralt: '}'
}
@@ -193,13 +205,15 @@
key LEFT_BRACKET {
label: '\u02c6'
base: '\u0302'
- shift: '\u0308'
+ shift, capslock: '\u0308'
+ shift+capslock: '\u0302'
}
key RIGHT_BRACKET {
label: '$'
base: '$'
- shift: '\u00a3'
+ shift, capslock: '\u00a3'
+ shift+capslock: '$'
ralt: '\u00a4'
}
@@ -278,13 +292,15 @@
key APOSTROPHE {
label: '\u00f9'
base: '\u00f9'
- shift: '%'
+ shift, capslock: '%'
+ shift+capslock: '\u00f9'
}
key BACKSLASH {
label: '*'
base: '*'
- shift: '\u00b5'
+ shift, capslock: '\u00b5'
+ shift+capslock: '*'
}
### ROW 4
@@ -340,23 +356,27 @@
key COMMA {
label: ','
base: ','
- shift: '?'
+ shift, capslock: '?'
+ shift+capslock: ','
}
key SEMICOLON {
label: ';'
base: ';'
- shift: '.'
+ shift, capslock: '.'
+ shift+capslock: ';'
}
key PERIOD {
label: ':'
base: ':'
- shift: '/'
+ shift, capslock: '/'
+ shift+capslock: ':'
}
key SLASH {
label: '!'
base: '!'
- shift: '\u00a7'
+ shift, capslock: '\u00a7'
+ shift+capslock: '!'
}
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 79c810c..bd84b58 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -46,7 +46,6 @@
sdk_version: "system_current",
rename_resources_package: false,
static_libs: [
- "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
@@ -79,7 +78,6 @@
overrides: ["PackageInstaller"],
static_libs: [
- "xz-java",
"androidx.leanback_leanback",
"androidx.fragment_fragment",
"androidx.lifecycle_lifecycle-livedata",
@@ -112,7 +110,6 @@
overrides: ["PackageInstaller"],
static_libs: [
- "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index bf69d3b..05f4d69 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -146,17 +146,6 @@
android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
android:exported="false" />
- <!-- Wearable Components -->
- <service android:name=".wear.WearPackageInstallerService"
- android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
- android:foregroundServiceType="systemExempted"
- android:exported="true"/>
-
- <provider android:name=".wear.WearPackageIconProvider"
- android:authorities="com.google.android.packageinstaller.wear.provider"
- android:grantUriPermissions="true"
- android:exported="false" />
-
<receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
tools:node="remove" />
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
deleted file mode 100644
index 53a460d..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2016 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.packageinstaller.wear;
-
-import android.content.Context;
-import android.content.IntentSender;
-import android.content.pm.PackageInstaller;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Task that installs an APK. This must not be called on the main thread.
- * This code is based off the Finsky/Wearsky implementation
- */
-public class InstallTask {
- private static final String TAG = "InstallTask";
-
- private static final int DEFAULT_BUFFER_SIZE = 8192;
-
- private final Context mContext;
- private String mPackageName;
- private ParcelFileDescriptor mParcelFileDescriptor;
- private PackageInstallerImpl.InstallListener mCallback;
- private PackageInstaller.Session mSession;
- private IntentSender mCommitCallback;
-
- private Exception mException = null;
- private int mErrorCode = 0;
- private String mErrorDesc = null;
-
- public InstallTask(Context context, String packageName,
- ParcelFileDescriptor parcelFileDescriptor,
- PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session,
- IntentSender commitCallback) {
- mContext = context;
- mPackageName = packageName;
- mParcelFileDescriptor = parcelFileDescriptor;
- mCallback = callback;
- mSession = session;
- mCommitCallback = commitCallback;
- }
-
- public boolean isError() {
- return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc);
- }
-
- public void execute() {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- throw new IllegalStateException("This method cannot be called from the UI thread.");
- }
-
- OutputStream sessionStream = null;
- try {
- sessionStream = mSession.openWrite(mPackageName, 0, -1);
-
- // 2b: Stream the asset to the installer. Note:
- // Note: writeToOutputStreamFromAsset() always safely closes the input stream
- writeToOutputStreamFromAsset(sessionStream);
- mSession.fsync(sessionStream);
- } catch (Exception e) {
- mException = e;
- mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM;
- mErrorDesc = "Could not write to stream";
- } finally {
- if (sessionStream != null) {
- // 2c: close output stream
- try {
- sessionStream.close();
- } catch (Exception e) {
- // Ignore otherwise
- if (mException == null) {
- mException = e;
- mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM;
- mErrorDesc = "Could not close session stream";
- }
- }
- }
- }
-
- if (mErrorCode != InstallerConstants.STATUS_SUCCESS) {
- // An error occurred, we're done
- Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", "
- + mErrorDesc + ", " + mException);
- mSession.close();
- mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc);
- } else {
- // 3. Commit the session (this actually installs it.) Session map
- // will be cleaned up in the callback.
- mCallback.installBeginning();
- mSession.commit(mCommitCallback);
- mSession.close();
- }
- }
-
- /**
- * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor}
- * corresponding to the {@code Asset} and then write the contents into an
- * {@code OutputStream} that is passed in.
- * <br>
- * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed.
- */
- private boolean writeToOutputStreamFromAsset(OutputStream outputStream) {
- if (outputStream == null) {
- mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION;
- mErrorDesc = "Got a null OutputStream.";
- return false;
- }
-
- if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null) {
- mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD;
- mErrorDesc = "Could not get FD";
- return false;
- }
-
- InputStream inputStream = null;
- try {
- byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE];
- int bytesRead;
- inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor);
-
- while ((bytesRead = inputStream.read(inputBuf)) > -1) {
- if (bytesRead > 0) {
- outputStream.write(inputBuf, 0, bytesRead);
- }
- }
-
- outputStream.flush();
- } catch (IOException e) {
- mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE;
- mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e;
- return false;
- } finally {
- safeClose(inputStream);
- }
-
- return true;
- }
-
- /**
- * Quietly close a closeable resource (e.g. a stream or file). The input may already
- * be closed and it may even be null.
- */
- public static void safeClose(Closeable resource) {
- if (resource != null) {
- try {
- resource.close();
- } catch (IOException ioe) {
- // Catch and discard the error
- }
- }
- }
-}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
deleted file mode 100644
index 3daf3d8..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2016 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.packageinstaller.wear;
-
-/**
- * Constants for Installation / Uninstallation requests.
- * Using the same values as Finsky/Wearsky code for consistency in user analytics of failures
- */
-public class InstallerConstants {
- /** Request succeeded */
- public static final int STATUS_SUCCESS = 0;
-
- /**
- * The new PackageInstaller also returns a small set of less granular error codes, which
- * we'll remap to the range -500 and below to keep away from existing installer codes
- * (which run from -1 to -110).
- */
- public final static int ERROR_PACKAGEINSTALLER_BASE = -500;
-
- public static final int ERROR_COULD_NOT_GET_FD = -603;
- /** This node is not targeted by this request. */
-
- /** The install did not complete because could not create PackageInstaller session */
- public final static int ERROR_INSTALL_CREATE_SESSION = -612;
- /** The install did not complete because could not open PackageInstaller session */
- public final static int ERROR_INSTALL_OPEN_SESSION = -613;
- /** The install did not complete because could not open PackageInstaller output stream */
- public final static int ERROR_INSTALL_OPEN_STREAM = -614;
- /** The install did not complete because of an exception while streaming bytes */
- public final static int ERROR_INSTALL_COPY_STREAM_EXCEPTION = -615;
- /** The install did not complete because of an unexpected exception from PackageInstaller */
- public final static int ERROR_INSTALL_SESSION_EXCEPTION = -616;
- /** The install did not complete because of an unexpected userActionRequired callback */
- public final static int ERROR_INSTALL_USER_ACTION_REQUIRED = -617;
- /** The install did not complete because of an unexpected broadcast (missing fields) */
- public final static int ERROR_INSTALL_MALFORMED_BROADCAST = -618;
- /** The install did not complete because of an error while copying from downloaded file */
- public final static int ERROR_INSTALL_APK_COPY_FAILURE = -619;
- /** The install did not complete because of an error while copying to the PackageInstaller
- * output stream */
- public final static int ERROR_INSTALL_COPY_STREAM = -620;
- /** The install did not complete because of an error while closing the PackageInstaller
- * output stream */
- public final static int ERROR_INSTALL_CLOSE_STREAM = -621;
-}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
deleted file mode 100644
index bdc22cf..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 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.packageinstaller.wear;
-
-import android.content.Context;
-
-/**
- * Factory that creates a Package Installer.
- */
-public class PackageInstallerFactory {
- private static PackageInstallerImpl sPackageInstaller;
-
- /**
- * Return the PackageInstaller shared object. {@code init} should have already been called.
- */
- public synchronized static PackageInstallerImpl getPackageInstaller(Context context) {
- if (sPackageInstaller == null) {
- sPackageInstaller = new PackageInstallerImpl(context);
- }
- return sPackageInstaller;
- }
-}
\ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
deleted file mode 100644
index 1e37f15..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2016 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.packageinstaller.wear;
-
-import android.annotation.TargetApi;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.pm.PackageInstaller;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Implementation of package manager installation using modern PackageInstaller api.
- *
- * Heavily copied from Wearsky/Finsky implementation
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class PackageInstallerImpl {
- private static final String TAG = "PackageInstallerImpl";
-
- /** Intent actions used for broadcasts from PackageInstaller back to the local receiver */
- private static final String ACTION_INSTALL_COMMIT =
- "com.android.vending.INTENT_PACKAGE_INSTALL_COMMIT";
-
- private final Context mContext;
- private final PackageInstaller mPackageInstaller;
- private final Map<String, PackageInstaller.SessionInfo> mSessionInfoMap;
- private final Map<String, PackageInstaller.Session> mOpenSessionMap;
-
- public PackageInstallerImpl(Context context) {
- mContext = context.getApplicationContext();
- mPackageInstaller = mContext.getPackageManager().getPackageInstaller();
-
- // Capture a map of known sessions
- // This list will be pruned a bit later (stale sessions will be canceled)
- mSessionInfoMap = new HashMap<String, PackageInstaller.SessionInfo>();
- List<PackageInstaller.SessionInfo> mySessions = mPackageInstaller.getMySessions();
- for (int i = 0; i < mySessions.size(); i++) {
- PackageInstaller.SessionInfo sessionInfo = mySessions.get(i);
- String packageName = sessionInfo.getAppPackageName();
- PackageInstaller.SessionInfo oldInfo = mSessionInfoMap.put(packageName, sessionInfo);
-
- // Checking for old info is strictly for logging purposes
- if (oldInfo != null) {
- Log.w(TAG, "Multiple sessions for " + packageName + " found. Removing " + oldInfo
- .getSessionId() + " & keeping " + mySessions.get(i).getSessionId());
- }
- }
- mOpenSessionMap = new HashMap<String, PackageInstaller.Session>();
- }
-
- /**
- * This callback will be made after an installation attempt succeeds or fails.
- */
- public interface InstallListener {
- /**
- * This callback signals that preflight checks have succeeded and installation
- * is beginning.
- */
- void installBeginning();
-
- /**
- * This callback signals that installation has completed.
- */
- void installSucceeded();
-
- /**
- * This callback signals that installation has failed.
- */
- void installFailed(int errorCode, String errorDesc);
- }
-
- /**
- * This is a placeholder implementation that bundles an entire "session" into a single
- * call. This will be replaced by more granular versions that allow longer session lifetimes,
- * download progress tracking, etc.
- *
- * This must not be called on main thread.
- */
- public void install(final String packageName, ParcelFileDescriptor parcelFileDescriptor,
- final InstallListener callback) {
- // 0. Generic try/catch block because I am not really sure what exceptions (other than
- // IOException) might be thrown by PackageInstaller and I want to handle them
- // at least slightly gracefully.
- try {
- // 1. Create or recover a session, and open it
- // Try recovery first
- PackageInstaller.Session session = null;
- PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
- if (sessionInfo != null) {
- // See if it's openable, or already held open
- session = getSession(packageName);
- }
- // If open failed, or there was no session, create a new one and open it.
- // If we cannot create or open here, the failure is terminal.
- if (session == null) {
- try {
- innerCreateSession(packageName);
- } catch (IOException ioe) {
- Log.e(TAG, "Can't create session for " + packageName + ": " + ioe.getMessage());
- callback.installFailed(InstallerConstants.ERROR_INSTALL_CREATE_SESSION,
- "Could not create session");
- mSessionInfoMap.remove(packageName);
- return;
- }
- sessionInfo = mSessionInfoMap.get(packageName);
- try {
- session = mPackageInstaller.openSession(sessionInfo.getSessionId());
- mOpenSessionMap.put(packageName, session);
- } catch (SecurityException se) {
- Log.e(TAG, "Can't open session for " + packageName + ": " + se.getMessage());
- callback.installFailed(InstallerConstants.ERROR_INSTALL_OPEN_SESSION,
- "Can't open session");
- mSessionInfoMap.remove(packageName);
- return;
- }
- }
-
- // 2. Launch task to handle file operations.
- InstallTask task = new InstallTask( mContext, packageName, parcelFileDescriptor,
- callback, session,
- getCommitCallback(packageName, sessionInfo.getSessionId(), callback));
- task.execute();
- if (task.isError()) {
- cancelSession(sessionInfo.getSessionId(), packageName);
- }
- } catch (Exception e) {
- Log.e(TAG, "Unexpected exception while installing: " + packageName + ": "
- + e.getMessage());
- callback.installFailed(InstallerConstants.ERROR_INSTALL_SESSION_EXCEPTION,
- "Unexpected exception while installing " + packageName);
- }
- }
-
- /**
- * Retrieve an existing session. Will open if needed, but does not attempt to create.
- */
- private PackageInstaller.Session getSession(String packageName) {
- // Check for already-open session
- PackageInstaller.Session session = mOpenSessionMap.get(packageName);
- if (session != null) {
- try {
- // Probe the session to ensure that it's still open. This may or may not
- // throw (if non-open), but it may serve as a canary for stale sessions.
- session.getNames();
- return session;
- } catch (IOException ioe) {
- Log.e(TAG, "Stale open session for " + packageName + ": " + ioe.getMessage());
- mOpenSessionMap.remove(packageName);
- } catch (SecurityException se) {
- Log.e(TAG, "Stale open session for " + packageName + ": " + se.getMessage());
- mOpenSessionMap.remove(packageName);
- }
- }
- // Check to see if this is a known session
- PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
- if (sessionInfo == null) {
- return null;
- }
- // Try to open it. If we fail here, assume that the SessionInfo was stale.
- try {
- session = mPackageInstaller.openSession(sessionInfo.getSessionId());
- } catch (SecurityException se) {
- Log.w(TAG, "SessionInfo was stale for " + packageName + " - deleting info");
- mSessionInfoMap.remove(packageName);
- return null;
- } catch (IOException ioe) {
- Log.w(TAG, "IOException opening old session for " + ioe.getMessage()
- + " - deleting info");
- mSessionInfoMap.remove(packageName);
- return null;
- }
- mOpenSessionMap.put(packageName, session);
- return session;
- }
-
- /** This version throws an IOException when the session cannot be created */
- private void innerCreateSession(String packageName) throws IOException {
- if (mSessionInfoMap.containsKey(packageName)) {
- Log.w(TAG, "Creating session for " + packageName + " when one already exists");
- return;
- }
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- params.setAppPackageName(packageName);
-
- // IOException may be thrown at this point
- int sessionId = mPackageInstaller.createSession(params);
- PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(sessionId);
- mSessionInfoMap.put(packageName, sessionInfo);
- }
-
- /**
- * Cancel a session based on its sessionId. Package name is for logging only.
- */
- private void cancelSession(int sessionId, String packageName) {
- // Close if currently held open
- closeSession(packageName);
- // Remove local record
- mSessionInfoMap.remove(packageName);
- try {
- mPackageInstaller.abandonSession(sessionId);
- } catch (SecurityException se) {
- // The session no longer exists, so we can exit quietly.
- return;
- }
- }
-
- /**
- * Close a session if it happens to be held open.
- */
- private void closeSession(String packageName) {
- PackageInstaller.Session session = mOpenSessionMap.remove(packageName);
- if (session != null) {
- // Unfortunately close() is not idempotent. Try our best to make this safe.
- try {
- session.close();
- } catch (Exception e) {
- Log.w(TAG, "Unexpected error closing session for " + packageName + ": "
- + e.getMessage());
- }
- }
- }
-
- /**
- * Creates a commit callback for the package install that's underway. This will be called
- * some time after calling session.commit() (above).
- */
- private IntentSender getCommitCallback(final String packageName, final int sessionId,
- final InstallListener callback) {
- // Create a single-use broadcast receiver
- BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mContext.unregisterReceiver(this);
- handleCommitCallback(intent, packageName, sessionId, callback);
- }
- };
- // Create a matching intent-filter and register the receiver
- String action = ACTION_INSTALL_COMMIT + "." + packageName;
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(action);
- mContext.registerReceiver(broadcastReceiver, intentFilter,
- Context.RECEIVER_EXPORTED);
-
- // Create a matching PendingIntent and use it to generate the IntentSender
- Intent broadcastIntent = new Intent(action).setPackage(mContext.getPackageName());
- PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, packageName.hashCode(),
- broadcastIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_MUTABLE);
- return pendingIntent.getIntentSender();
- }
-
- /**
- * Examine the extras to determine information about the package update/install, decode
- * the result, and call the appropriate callback.
- *
- * @param intent The intent, which the PackageInstaller will have added Extras to
- * @param packageName The package name we created the receiver for
- * @param sessionId The session Id we created the receiver for
- * @param callback The callback to report success/failure to
- */
- private void handleCommitCallback(Intent intent, String packageName, int sessionId,
- InstallListener callback) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Installation of " + packageName + " finished with extras "
- + intent.getExtras());
- }
- String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
- int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MIN_VALUE);
- if (status == PackageInstaller.STATUS_SUCCESS) {
- cancelSession(sessionId, packageName);
- callback.installSucceeded();
- } else if (status == -1 /*PackageInstaller.STATUS_USER_ACTION_REQUIRED*/) {
- // TODO - use the constant when the correct/final name is in the SDK
- // TODO This is unexpected, so we are treating as failure for now
- cancelSession(sessionId, packageName);
- callback.installFailed(InstallerConstants.ERROR_INSTALL_USER_ACTION_REQUIRED,
- "Unexpected: user action required");
- } else {
- cancelSession(sessionId, packageName);
- int errorCode = getPackageManagerErrorCode(status);
- Log.e(TAG, "Error " + errorCode + " while installing " + packageName + ": "
- + statusMessage);
- callback.installFailed(errorCode, null);
- }
- }
-
- private int getPackageManagerErrorCode(int status) {
- // This is a hack: because PackageInstaller now reports error codes
- // with small positive values, we need to remap them into a space
- // that is more compatible with the existing package manager error codes.
- // See https://sites.google.com/a/google.com/universal-store/documentation
- // /android-client/download-error-codes
- int errorCode;
- if (status == Integer.MIN_VALUE) {
- errorCode = InstallerConstants.ERROR_INSTALL_MALFORMED_BROADCAST;
- } else {
- errorCode = InstallerConstants.ERROR_PACKAGEINSTALLER_BASE - status;
- }
- return errorCode;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
deleted file mode 100644
index 2c289b2..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2015 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.packageinstaller.wear;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-
-/**
- * Installation Util that contains a list of parameters that are needed for
- * installing/uninstalling.
- */
-public class WearPackageArgs {
- private static final String KEY_PACKAGE_NAME =
- "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
- private static final String KEY_ASSET_URI =
- "com.google.android.clockwork.EXTRA_ASSET_URI";
- private static final String KEY_START_ID =
- "com.google.android.clockwork.EXTRA_START_ID";
- private static final String KEY_PERM_URI =
- "com.google.android.clockwork.EXTRA_PERM_URI";
- private static final String KEY_CHECK_PERMS =
- "com.google.android.clockwork.EXTRA_CHECK_PERMS";
- private static final String KEY_SKIP_IF_SAME_VERSION =
- "com.google.android.clockwork.EXTRA_SKIP_IF_SAME_VERSION";
- private static final String KEY_COMPRESSION_ALG =
- "com.google.android.clockwork.EXTRA_KEY_COMPRESSION_ALG";
- private static final String KEY_COMPANION_SDK_VERSION =
- "com.google.android.clockwork.EXTRA_KEY_COMPANION_SDK_VERSION";
- private static final String KEY_COMPANION_DEVICE_VERSION =
- "com.google.android.clockwork.EXTRA_KEY_COMPANION_DEVICE_VERSION";
- private static final String KEY_SHOULD_CHECK_GMS_DEPENDENCY =
- "com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY";
- private static final String KEY_SKIP_IF_LOWER_VERSION =
- "com.google.android.clockwork.EXTRA_SKIP_IF_LOWER_VERSION";
-
- public static String getPackageName(Bundle b) {
- return b.getString(KEY_PACKAGE_NAME);
- }
-
- public static Bundle setPackageName(Bundle b, String packageName) {
- b.putString(KEY_PACKAGE_NAME, packageName);
- return b;
- }
-
- public static Uri getAssetUri(Bundle b) {
- return b.getParcelable(KEY_ASSET_URI);
- }
-
- public static Uri getPermUri(Bundle b) {
- return b.getParcelable(KEY_PERM_URI);
- }
-
- public static boolean checkPerms(Bundle b) {
- return b.getBoolean(KEY_CHECK_PERMS);
- }
-
- public static boolean skipIfSameVersion(Bundle b) {
- return b.getBoolean(KEY_SKIP_IF_SAME_VERSION);
- }
-
- public static int getCompanionSdkVersion(Bundle b) {
- return b.getInt(KEY_COMPANION_SDK_VERSION);
- }
-
- public static int getCompanionDeviceVersion(Bundle b) {
- return b.getInt(KEY_COMPANION_DEVICE_VERSION);
- }
-
- public static String getCompressionAlg(Bundle b) {
- return b.getString(KEY_COMPRESSION_ALG);
- }
-
- public static int getStartId(Bundle b) {
- return b.getInt(KEY_START_ID);
- }
-
- public static boolean skipIfLowerVersion(Bundle b) {
- return b.getBoolean(KEY_SKIP_IF_LOWER_VERSION, false);
- }
-
- public static Bundle setStartId(Bundle b, int startId) {
- b.putInt(KEY_START_ID, startId);
- return b;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
deleted file mode 100644
index 02b9d29..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2015 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.packageinstaller.wear;
-
-import android.annotation.TargetApi;
-import android.app.ActivityManager;
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.List;
-
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
-public class WearPackageIconProvider extends ContentProvider {
- private static final String TAG = "WearPackageIconProvider";
- public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";
-
- private static final String REQUIRED_PERMISSION =
- "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";
-
- /** MIME types. */
- public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";
-
- @Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- throw new UnsupportedOperationException("Query is not supported.");
- }
-
- @Override
- public String getType(Uri uri) {
- if (uri == null) {
- throw new IllegalArgumentException("URI passed in is null.");
- }
-
- if (AUTHORITY.equals(uri.getEncodedAuthority())) {
- return ICON_TYPE;
- }
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- throw new UnsupportedOperationException("Insert is not supported.");
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- if (uri == null) {
- throw new IllegalArgumentException("URI passed in is null.");
- }
-
- enforcePermissions(uri);
-
- if (ICON_TYPE.equals(getType(uri))) {
- final File file = WearPackageUtil.getIconFile(
- this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
- if (file != null) {
- file.delete();
- }
- }
-
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException("Update is not supported.");
- }
-
- @Override
- public ParcelFileDescriptor openFile(
- Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
- if (uri == null) {
- throw new IllegalArgumentException("URI passed in is null.");
- }
-
- enforcePermissions(uri);
-
- if (ICON_TYPE.equals(getType(uri))) {
- final File file = WearPackageUtil.getIconFile(
- this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
- if (file != null) {
- return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
- }
- }
- return null;
- }
-
- public static Uri getUriForPackage(final String packageName) {
- return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
- }
-
- private String getPackageNameFromUri(Uri uri) {
- if (uri == null) {
- return null;
- }
- List<String> pathSegments = uri.getPathSegments();
- String packageName = pathSegments.get(pathSegments.size() - 1);
-
- if (packageName.endsWith(".icon")) {
- packageName = packageName.substring(0, packageName.lastIndexOf("."));
- }
- return packageName;
- }
-
- /**
- * Make sure the calling app is either a system app or the same app or has the right permission.
- * @throws SecurityException if the caller has insufficient permissions.
- */
- @TargetApi(Build.VERSION_CODES.BASE_1_1)
- private void enforcePermissions(Uri uri) {
- // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
- // allow System process to access this provider.
- Context context = getContext();
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final int myUid = android.os.Process.myUid();
-
- if (uid == myUid || isSystemApp(context, pid)) {
- return;
- }
-
- if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
- return;
- }
-
- // last chance, check against any uri grants
- if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
- == PERMISSION_GRANTED) {
- return;
- }
-
- throw new SecurityException("Permission Denial: reading "
- + getClass().getName() + " uri " + uri + " from pid=" + pid
- + ", uid=" + uid);
- }
-
- /**
- * From the pid of the calling process, figure out whether this is a system app or not. We do
- * this by checking the application information corresponding to the pid and then checking if
- * FLAG_SYSTEM is set.
- */
- @TargetApi(Build.VERSION_CODES.CUPCAKE)
- private boolean isSystemApp(Context context, int pid) {
- // Get the Activity Manager Object
- ActivityManager aManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- // Get the list of running Applications
- List<ActivityManager.RunningAppProcessInfo> rapInfoList =
- aManager.getRunningAppProcesses();
- for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
- if (rapInfo.pid == pid) {
- try {
- PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
- rapInfo.pkgList[0], 0);
- if (pkgInfo != null && pkgInfo.applicationInfo != null &&
- (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- Log.d(TAG, pid + " is a system app.");
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Could not find package information.", e);
- return false;
- }
- }
- }
- return false;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
deleted file mode 100644
index ae0f4ec..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
+++ /dev/null
@@ -1,621 +0,0 @@
-/*
- * Copyright (C) 2015 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.packageinstaller.wear;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.FeatureInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
-import android.os.Process;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-import androidx.annotation.Nullable;
-import com.android.packageinstaller.DeviceUtils;
-import com.android.packageinstaller.PackageUtil;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.UninstallEventReceiver;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Service that will install/uninstall packages. It will check for permissions and features as well.
- *
- * -----------
- *
- * Debugging information:
- *
- * Install Action example:
- * adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \
- * -d package://com.google.android.gms \
- * --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
- * --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \
- * --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \
- * --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \
- * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
- *
- * Uninstall Action example:
- * adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \
- * -d package://com.google.android.gms \
- * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
- *
- * Retry GMS:
- * adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
- * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
- */
-public class WearPackageInstallerService extends Service
- implements EventResultPersister.EventResultObserver {
- private static final String TAG = "WearPkgInstallerService";
-
- private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
- private static final String BROADCAST_ACTION =
- "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
-
- private final int START_INSTALL = 1;
- private final int START_UNINSTALL = 2;
-
- private int mInstallNotificationId = 1;
- private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
- private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
-
- private class UninstallParams {
- public String mPackageName;
- public PowerManager.WakeLock mLock;
-
- UninstallParams(String packageName, PowerManager.WakeLock lock) {
- mPackageName = packageName;
- mLock = lock;
- }
- }
-
- private final class ServiceHandler extends Handler {
- public ServiceHandler(Looper looper) {
- super(looper);
- }
-
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case START_INSTALL:
- installPackage(msg.getData());
- break;
- case START_UNINSTALL:
- uninstallPackage(msg.getData());
- break;
- }
- }
- }
- private ServiceHandler mServiceHandler;
- private NotificationChannel mNotificationChannel;
- private static volatile PowerManager.WakeLock lockStatic = null;
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- HandlerThread thread = new HandlerThread("PackageInstallerThread",
- Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
-
- mServiceHandler = new ServiceHandler(thread.getLooper());
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (!DeviceUtils.isWear(this)) {
- Log.w(TAG, "Not running on wearable.");
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- if (intent == null) {
- Log.w(TAG, "Got null intent.");
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Got install/uninstall request " + intent);
- }
-
- Uri packageUri = intent.getData();
- if (packageUri == null) {
- Log.e(TAG, "No package URI in intent");
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
- if (packageName == null) {
- Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- PowerManager.WakeLock lock = getLock(this.getApplicationContext());
- if (!lock.isHeld()) {
- lock.acquire();
- }
-
- Bundle intentBundle = intent.getExtras();
- if (intentBundle == null) {
- intentBundle = new Bundle();
- }
- WearPackageArgs.setStartId(intentBundle, startId);
- WearPackageArgs.setPackageName(intentBundle, packageName);
- Message msg;
- String notifTitle;
- if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
- msg = mServiceHandler.obtainMessage(START_INSTALL);
- notifTitle = getString(R.string.installing);
- } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
- msg = mServiceHandler.obtainMessage(START_UNINSTALL);
- notifTitle = getString(R.string.uninstalling);
- } else {
- Log.e(TAG, "Unknown action : " + intent.getAction());
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
- Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle);
- startForeground(notifPair.first, notifPair.second);
- msg.setData(intentBundle);
- mServiceHandler.sendMessage(msg);
- return START_NOT_STICKY;
- }
-
- private void installPackage(Bundle argsBundle) {
- int startId = WearPackageArgs.getStartId(argsBundle);
- final String packageName = WearPackageArgs.getPackageName(argsBundle);
- final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle);
- final Uri permUri = WearPackageArgs.getPermUri(argsBundle);
- boolean checkPerms = WearPackageArgs.checkPerms(argsBundle);
- boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle);
- int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle);
- int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle);
- String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle);
- boolean skipIfLowerVersion = WearPackageArgs.skipIfLowerVersion(argsBundle);
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri +
- ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " +
- checkPerms + ", skipIfSameVersion: " + skipIfSameVersion +
- ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " +
- companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion +
- ", skipIfLowerVersion: " + skipIfLowerVersion);
- }
- final PackageManager pm = getPackageManager();
- File tempFile = null;
- PowerManager.WakeLock lock = getLock(this.getApplicationContext());
- boolean messageSent = false;
- try {
- PackageInfo existingPkgInfo = null;
- try {
- existingPkgInfo = pm.getPackageInfo(packageName,
- PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
- if (existingPkgInfo != null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Replacing package:" + packageName);
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Ignore this exception. We could not find the package, will treat as a new
- // installation.
- }
- // TODO(28021618): This was left as a temp file due to the fact that this code is being
- // deprecated and that we need the bare minimum to continue working moving forward
- // If this code is used as reference, this permission logic might want to be
- // reworked to use a stream instead of a file so that we don't need to write a
- // file at all. Note that there might be some trickiness with opening a stream
- // for multiple users.
- ParcelFileDescriptor parcelFd = getContentResolver()
- .openFileDescriptor(assetUri, "r");
- tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this,
- parcelFd, packageName, compressionAlg);
- if (tempFile == null) {
- Log.e(TAG, "Could not create a temp file from FD for " + packageName);
- return;
- }
- PackageInfo pkgInfo = PackageUtil.getPackageInfo(this, tempFile,
- PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS);
- if (pkgInfo == null) {
- Log.e(TAG, "Could not parse apk information for " + packageName);
- return;
- }
-
- if (!pkgInfo.packageName.equals(packageName)) {
- Log.e(TAG, "Wearable Package Name has to match what is provided for " +
- packageName);
- return;
- }
-
- ApplicationInfo appInfo = pkgInfo.applicationInfo;
- appInfo.sourceDir = tempFile.getPath();
- appInfo.publicSourceDir = tempFile.getPath();
- getLabelAndUpdateNotification(packageName,
- getString(R.string.installing_app, appInfo.loadLabel(pm)));
-
- List<String> wearablePerms = Arrays.asList(pkgInfo.requestedPermissions);
-
- // Log if the installed pkg has a higher version number.
- if (existingPkgInfo != null) {
- long longVersionCode = pkgInfo.getLongVersionCode();
- if (existingPkgInfo.getLongVersionCode() == longVersionCode) {
- if (skipIfSameVersion) {
- Log.w(TAG, "Version number (" + longVersionCode +
- ") of new app is equal to existing app for " + packageName +
- "; not installing due to versionCheck");
- return;
- } else {
- Log.w(TAG, "Version number of new app (" + longVersionCode +
- ") is equal to existing app for " + packageName);
- }
- } else if (existingPkgInfo.getLongVersionCode() > longVersionCode) {
- if (skipIfLowerVersion) {
- // Starting in Feldspar, we are not going to allow downgrades of any app.
- Log.w(TAG, "Version number of new app (" + longVersionCode +
- ") is lower than existing app ( "
- + existingPkgInfo.getLongVersionCode() +
- ") for " + packageName + "; not installing due to versionCheck");
- return;
- } else {
- Log.w(TAG, "Version number of new app (" + longVersionCode +
- ") is lower than existing app ( "
- + existingPkgInfo.getLongVersionCode() + ") for " + packageName);
- }
- }
-
- // Following the Android Phone model, we should only check for permissions for any
- // newly defined perms.
- if (existingPkgInfo.requestedPermissions != null) {
- for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) {
- // If the permission is granted, then we will not ask to request it again.
- if ((existingPkgInfo.requestedPermissionsFlags[i] &
- PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, existingPkgInfo.requestedPermissions[i] +
- " is already granted for " + packageName);
- }
- wearablePerms.remove(existingPkgInfo.requestedPermissions[i]);
- }
- }
- }
- }
-
- // Check that the wearable has all the features.
- boolean hasAllFeatures = true;
- for (FeatureInfo feature : pkgInfo.reqFeatures) {
- if (feature.name != null && !pm.hasSystemFeature(feature.name) &&
- (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) {
- Log.e(TAG, "Wearable does not have required feature: " + feature +
- " for " + packageName);
- hasAllFeatures = false;
- }
- }
-
- if (!hasAllFeatures) {
- return;
- }
-
- // Check permissions on both the new wearable package and also on the already installed
- // wearable package.
- // If the app is targeting API level 23, we will also start a service in ClockworkHome
- // which will ultimately prompt the user to accept/reject permissions.
- if (checkPerms && !checkPermissions(pkgInfo, companionSdkVersion,
- companionDeviceVersion, permUri, wearablePerms, tempFile)) {
- Log.w(TAG, "Wearable does not have enough permissions.");
- return;
- }
-
- // Finally install the package.
- ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r");
- PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd,
- new PackageInstallListener(this, lock, startId, packageName));
-
- messageSent = true;
- Log.i(TAG, "Sent installation request for " + packageName);
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Could not find the file with URI " + assetUri, e);
- } finally {
- if (!messageSent) {
- // Some error happened. If the message has been sent, we can wait for the observer
- // which will finish the service.
- if (tempFile != null) {
- tempFile.delete();
- }
- finishService(lock, startId);
- }
- }
- }
-
- // TODO: This was left using the old PackageManager API due to the fact that this code is being
- // deprecated and that we need the bare minimum to continue working moving forward
- // If this code is used as reference, this logic should be reworked to use the new
- // PackageInstaller APIs similar to how installPackage was reworked
- private void uninstallPackage(Bundle argsBundle) {
- int startId = WearPackageArgs.getStartId(argsBundle);
- final String packageName = WearPackageArgs.getPackageName(argsBundle);
-
- PowerManager.WakeLock lock = getLock(this.getApplicationContext());
-
- UninstallParams params = new UninstallParams(packageName, lock);
- mServiceIdToParams.put(startId, params);
-
- final PackageManager pm = getPackageManager();
- try {
- PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
- getLabelAndUpdateNotification(packageName,
- getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
-
- int uninstallId = UninstallEventReceiver.addObserver(this,
- EventResultPersister.GENERATE_NEW_ID, this);
-
- Intent broadcastIntent = new Intent(BROADCAST_ACTION);
- broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
- broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
- broadcastIntent.setPackage(getPackageName());
-
- PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
- broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_MUTABLE);
-
- // Found package, send uninstall request.
- pm.getPackageInstaller().uninstall(
- new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
- PackageManager.DELETE_ALL_USERS,
- pendingIntent.getIntentSender());
-
- Log.i(TAG, "Sent delete request for " + packageName);
- } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
- // Couldn't find the package, no need to call uninstall.
- Log.w(TAG, "Could not find package, not deleting " + packageName, e);
- finishService(lock, startId);
- } catch (EventResultPersister.OutOfIdsException e) {
- Log.e(TAG, "Fails to start uninstall", e);
- finishService(lock, startId);
- }
- }
-
- @Override
- public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
- if (mServiceIdToParams.containsKey(serviceId)) {
- UninstallParams params = mServiceIdToParams.get(serviceId);
- try {
- if (status == PackageInstaller.STATUS_SUCCESS) {
- Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
- } else {
- Log.e(TAG, "Package uninstall failed " + params.mPackageName
- + ", returnCode " + legacyStatus);
- }
- } finally {
- finishService(params.mLock, serviceId);
- }
- }
- }
-
- private boolean checkPermissions(PackageInfo pkgInfo, int companionSdkVersion,
- int companionDeviceVersion, Uri permUri, List<String> wearablePermissions,
- File apkFile) {
- // Assumption: We are running on Android O.
- // If the Phone App is targeting M, all permissions may not have been granted to the phone
- // app. If the Wear App is then not targeting M, there may be permissions that are not
- // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear
- // app.
- if (pkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
- // Install the app if Wear App is ready for the new perms model.
- return true;
- }
-
- if (!doesWearHaveUngrantedPerms(pkgInfo.packageName, permUri, wearablePermissions)) {
- // All permissions requested by the watch are already granted on the phone, no need
- // to do anything.
- return true;
- }
-
- // Log an error if Wear is targeting < 23 and phone is targeting >= 23.
- if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) {
- Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if "
- + "phone app is targeting at least 23, will continue.");
- }
-
- return false;
- }
-
- /**
- * Given a {@string packageName} corresponding to a phone app, query the provider for all the
- * perms that are granted.
- *
- * @return true if the Wear App has any perms that have not been granted yet on the phone side.
- * @return true if there is any error cases.
- */
- private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri,
- List<String> wearablePermissions) {
- if (permUri == null) {
- Log.e(TAG, "Permission URI is null");
- // Pretend there is an ungranted permission to avoid installing for error cases.
- return true;
- }
- Cursor permCursor = getContentResolver().query(permUri, null, null, null, null);
- if (permCursor == null) {
- Log.e(TAG, "Could not get the cursor for the permissions");
- // Pretend there is an ungranted permission to avoid installing for error cases.
- return true;
- }
-
- Set<String> grantedPerms = new HashSet<>();
- Set<String> ungrantedPerms = new HashSet<>();
- while(permCursor.moveToNext()) {
- // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and
- // verify their types.
- if (permCursor.getColumnCount() == 2
- && Cursor.FIELD_TYPE_STRING == permCursor.getType(0)
- && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) {
- String perm = permCursor.getString(0);
- Integer granted = permCursor.getInt(1);
- if (granted == 1) {
- grantedPerms.add(perm);
- } else {
- ungrantedPerms.add(perm);
- }
- }
- }
- permCursor.close();
-
- boolean hasUngrantedPerm = false;
- for (String wearablePerm : wearablePermissions) {
- if (!grantedPerms.contains(wearablePerm)) {
- hasUngrantedPerm = true;
- if (!ungrantedPerms.contains(wearablePerm)) {
- // This is an error condition. This means that the wearable has permissions that
- // are not even declared in its host app. This is a developer error.
- Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm
- + "\" that is not defined in the host application's manifest.");
- } else {
- Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm +
- "\" that is not granted in the host application.");
- }
- }
- }
- return hasUngrantedPerm;
- }
-
- /** Finishes the service after fulfilling obligation to call startForeground. */
- private void finishServiceEarly(int startId) {
- Pair<Integer, Notification> notifPair = buildNotification(
- getApplicationContext().getPackageName(), "");
- startForeground(notifPair.first, notifPair.second);
- finishService(null, startId);
- }
-
- private void finishService(PowerManager.WakeLock lock, int startId) {
- if (lock != null && lock.isHeld()) {
- lock.release();
- }
- stopSelf(startId);
- }
-
- private synchronized PowerManager.WakeLock getLock(Context context) {
- if (lockStatic == null) {
- PowerManager mgr =
- (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- lockStatic = mgr.newWakeLock(
- PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName());
- lockStatic.setReferenceCounted(true);
- }
- return lockStatic;
- }
-
- private class PackageInstallListener implements PackageInstallerImpl.InstallListener {
- private Context mContext;
- private PowerManager.WakeLock mWakeLock;
- private int mStartId;
- private String mApplicationPackageName;
- private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock,
- int startId, String applicationPackageName) {
- mContext = context;
- mWakeLock = wakeLock;
- mStartId = startId;
- mApplicationPackageName = applicationPackageName;
- }
-
- @Override
- public void installBeginning() {
- Log.i(TAG, "Package " + mApplicationPackageName + " is being installed.");
- }
-
- @Override
- public void installSucceeded() {
- try {
- Log.i(TAG, "Package " + mApplicationPackageName + " was installed.");
-
- // Delete tempFile from the file system.
- File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName);
- if (tempFile != null) {
- tempFile.delete();
- }
- } finally {
- finishService(mWakeLock, mStartId);
- }
- }
-
- @Override
- public void installFailed(int errorCode, String errorDesc) {
- Log.e(TAG, "Package install failed " + mApplicationPackageName
- + ", errorCode " + errorCode);
- finishService(mWakeLock, mStartId);
- }
- }
-
- private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
- final String title) {
- int notifId;
- if (mNotifIdMap.containsKey(packageName)) {
- notifId = mNotifIdMap.get(packageName);
- } else {
- notifId = mInstallNotificationId++;
- mNotifIdMap.put(packageName, notifId);
- }
-
- if (mNotificationChannel == null) {
- mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL,
- getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN);
- NotificationManager notificationManager = getSystemService(NotificationManager.class);
- notificationManager.createNotificationChannel(mNotificationChannel);
- }
- return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL)
- .setSmallIcon(R.drawable.ic_file_download)
- .setContentTitle(title)
- .build());
- }
-
- private void getLabelAndUpdateNotification(String packageName, String title) {
- // Update notification since we have a label now.
- NotificationManager notificationManager = getSystemService(NotificationManager.class);
- Pair<Integer, Notification> notifPair = buildNotification(packageName, title);
- notificationManager.notify(notifPair.first, notifPair.second);
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
deleted file mode 100644
index 6a9145d..0000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2015 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.packageinstaller.wear;
-
-import android.content.Context;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.tukaani.xz.LZMAInputStream;
-import org.tukaani.xz.XZInputStream;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class WearPackageUtil {
- private static final String TAG = "WearablePkgInstaller";
-
- private static final String COMPRESSION_LZMA = "lzma";
- private static final String COMPRESSION_XZ = "xz";
-
- public static File getTemporaryFile(Context context, String packageName) {
- try {
- File newFileDir = new File(context.getFilesDir(), "tmp");
- newFileDir.mkdirs();
- Os.chmod(newFileDir.getAbsolutePath(), 0771);
- File newFile = new File(newFileDir, packageName + ".apk");
- return newFile;
- } catch (ErrnoException e) {
- Log.e(TAG, "Failed to open.", e);
- return null;
- }
- }
-
- public static File getIconFile(final Context context, final String packageName) {
- try {
- File newFileDir = new File(context.getFilesDir(), "images/icons");
- newFileDir.mkdirs();
- Os.chmod(newFileDir.getAbsolutePath(), 0771);
- return new File(newFileDir, packageName + ".icon");
- } catch (ErrnoException e) {
- Log.e(TAG, "Failed to open.", e);
- return null;
- }
- }
-
- /**
- * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used
- * by the PackageManager, we will parse it before sending it to the PackageManager.
- * Unfortunately, ParsingPackageUtils needs a file to parse. So, we have to temporarily convert
- * the fd to a File.
- *
- * @param context
- * @param fd FileDescriptor to convert to File
- * @param packageName Name of package, will define the name of the file
- * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will
- * decompress it here
- */
- public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
- String packageName, String compressionAlg) {
- File newFile = getTemporaryFile(context, packageName);
- if (fd == null || fd.getFileDescriptor() == null) {
- return null;
- }
- InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd);
- try {
- if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) {
- fr = new XZInputStream(fr);
- } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) {
- fr = new LZMAInputStream(fr);
- }
- } catch (IOException e) {
- Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e);
- return null;
- }
-
- int nRead;
- byte[] data = new byte[1024];
- try {
- final FileOutputStream fo = new FileOutputStream(newFile);
- while ((nRead = fr.read(data, 0, data.length)) != -1) {
- fo.write(data, 0, nRead);
- }
- fo.flush();
- fo.close();
- Os.chmod(newFile.getAbsolutePath(), 0644);
- return newFile;
- } catch (IOException e) {
- Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e);
- return null;
- } catch (ErrnoException e) {
- Log.e(TAG, "Could not set permissions on file ", e);
- return null;
- } finally {
- try {
- fr.close();
- } catch (IOException e) {
- Log.e(TAG, "Failed to close the file from FD ", e);
- }
- }
- }
-
- /**
- * @return com.google.com from expected formats like
- * Uri: package:com.google.com, package:/com.google.com, package://com.google.com
- */
- public static String getSanitizedPackageName(Uri packageUri) {
- String packageName = packageUri.getEncodedSchemeSpecificPart();
- if (packageName != null) {
- return packageName.replaceAll("^/+", "");
- }
- return packageName;
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
index 8b546b4..791893b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetSwitchPreference.kt
@@ -23,7 +23,6 @@
@Composable
fun TwoTargetSwitchPreference(
model: SwitchPreferenceModel,
- icon: @Composable (() -> Unit)? = null,
primaryEnabled: () -> Boolean = { true },
primaryOnClick: (() -> Unit)?,
) {
@@ -33,7 +32,7 @@
summary = model.summary,
primaryEnabled = primaryEnabled,
primaryOnClick = primaryOnClick,
- icon = icon,
+ icon = model.icon,
) {
SettingsSwitch(
checked = model.checked(),
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
index 5c2d770..1f7122e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListTwoTargetSwitchItem.kt
@@ -33,11 +33,13 @@
model = object : SwitchPreferenceModel {
override val title = label
override val summary = this@AppListTwoTargetSwitchItem.summary
+ override val icon = @Composable {
+ AppIcon(record.app, SettingsDimension.appIconItemSize)
+ }
override val checked = checked
override val changeable = changeable
override val onCheckedChange = onCheckedChange
},
- icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
primaryOnClick = onClick,
)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index 87cd2b8..c9934ad 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -52,6 +52,8 @@
checked = model.checked,
)
+ override val icon = model.icon
+
override val checked = when (restrictedMode) {
null -> ({ null })
is NoRestricted -> model.checked
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
index e100773..1bed733 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedTwoTargetSwitchPreference.kt
@@ -29,14 +29,12 @@
@Composable
fun RestrictedTwoTargetSwitchPreference(
model: SwitchPreferenceModel,
- icon: @Composable (() -> Unit)? = null,
restrictions: Restrictions,
primaryEnabled: () -> Boolean = { true },
primaryOnClick: (() -> Unit)?,
) {
RestrictedTwoTargetSwitchPreference(
model = model,
- icon = icon,
primaryEnabled = primaryEnabled,
primaryOnClick = primaryOnClick,
restrictions = restrictions,
@@ -48,21 +46,19 @@
@Composable
internal fun RestrictedTwoTargetSwitchPreference(
model: SwitchPreferenceModel,
- icon: @Composable (() -> Unit)? = null,
primaryEnabled: () -> Boolean = { true },
primaryOnClick: (() -> Unit)?,
restrictions: Restrictions,
restrictionsProviderFactory: RestrictionsProviderFactory,
) {
if (restrictions.isEmpty()) {
- TwoTargetSwitchPreference(model, icon, primaryEnabled, primaryOnClick)
+ TwoTargetSwitchPreference(model, primaryEnabled, primaryOnClick)
return
}
val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value
RestrictedSwitchWrapper(model, restrictedMode) { restrictedModel ->
TwoTargetSwitchPreference(
model = restrictedModel,
- icon = icon,
primaryEnabled = restrictedMode.restrictEnabled(primaryEnabled),
primaryOnClick = restrictedMode.restrictOnClick(primaryOnClick),
)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 57fcc74..a906875 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -3,10 +3,13 @@
import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED;
import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -30,6 +33,7 @@
import androidx.core.graphics.drawable.IconCompat;
import com.android.settingslib.R;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.settingslib.widget.AdaptiveOutlineDrawable;
@@ -46,14 +50,14 @@
private static final String TAG = "BluetoothUtils";
public static final boolean V = false; // verbose logging
- public static final boolean D = true; // regular logging
+ public static final boolean D = true; // regular logging
public static final int META_INT_ERROR = -1;
public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
- private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of(
- "com.google.android.gms.dck");
+ private static final Set<String> EXCLUSIVE_MANAGERS =
+ ImmutableSet.of("com.google.android.gms.dck");
private static ErrorListener sErrorListener;
@@ -89,23 +93,23 @@
/**
* @param context to access resources from
* @param cachedDevice to get class from
- * @return pair containing the drawable and the description of the Bluetooth class
- * of the device.
+ * @return pair containing the drawable and the description of the Bluetooth class of the
+ * device.
*/
- public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context,
- CachedBluetoothDevice cachedDevice) {
+ public static Pair<Drawable, String> getBtClassDrawableWithDescription(
+ Context context, CachedBluetoothDevice cachedDevice) {
BluetoothClass btClass = cachedDevice.getBtClass();
if (btClass != null) {
switch (btClass.getMajorDeviceClass()) {
case BluetoothClass.Device.Major.COMPUTER:
- return new Pair<>(getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_laptop),
+ return new Pair<>(
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_laptop),
context.getString(R.string.bluetooth_talkback_computer));
case BluetoothClass.Device.Major.PHONE:
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_phone),
+ getBluetoothDrawable(context, com.android.internal.R.drawable.ic_phone),
context.getString(R.string.bluetooth_talkback_phone));
case BluetoothClass.Device.Major.PERIPHERAL:
@@ -115,8 +119,8 @@
case BluetoothClass.Device.Major.IMAGING:
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_settings_print),
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_settings_print),
context.getString(R.string.bluetooth_talkback_imaging));
default:
@@ -125,8 +129,9 @@
}
if (cachedDevice.isHearingAidDevice()) {
- return new Pair<>(getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_hearing_aid),
+ return new Pair<>(
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_hearing_aid),
context.getString(R.string.bluetooth_talkback_hearing_aids));
}
@@ -138,7 +143,8 @@
// The device should show hearing aid icon if it contains any hearing aid related
// profiles
if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) {
- return new Pair<>(getBluetoothDrawable(context, profileResId),
+ return new Pair<>(
+ getBluetoothDrawable(context, profileResId),
context.getString(R.string.bluetooth_talkback_hearing_aids));
}
if (resId == 0) {
@@ -153,42 +159,40 @@
if (btClass != null) {
if (doesClassMatch(btClass, BluetoothClass.PROFILE_HEADSET)) {
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_headset_hfp),
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_headset_hfp),
context.getString(R.string.bluetooth_talkback_headset));
}
if (doesClassMatch(btClass, BluetoothClass.PROFILE_A2DP)) {
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_bt_headphones_a2dp),
+ getBluetoothDrawable(
+ context, com.android.internal.R.drawable.ic_bt_headphones_a2dp),
context.getString(R.string.bluetooth_talkback_headphone));
}
}
return new Pair<>(
- getBluetoothDrawable(context,
- com.android.internal.R.drawable.ic_settings_bluetooth).mutate(),
+ getBluetoothDrawable(context, com.android.internal.R.drawable.ic_settings_bluetooth)
+ .mutate(),
context.getString(R.string.bluetooth_talkback_bluetooth));
}
- /**
- * Get bluetooth drawable by {@code resId}
- */
+ /** Get bluetooth drawable by {@code resId} */
public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId) {
return context.getDrawable(resId);
}
- /**
- * Get colorful bluetooth icon with description
- */
- public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(Context context,
- CachedBluetoothDevice cachedDevice) {
+ /** Get colorful bluetooth icon with description */
+ public static Pair<Drawable, String> getBtRainbowDrawableWithDescription(
+ Context context, CachedBluetoothDevice cachedDevice) {
final Resources resources = context.getResources();
- final Pair<Drawable, String> pair = BluetoothUtils.getBtDrawableWithDescription(context,
- cachedDevice);
+ final Pair<Drawable, String> pair =
+ BluetoothUtils.getBtDrawableWithDescription(context, cachedDevice);
if (pair.first instanceof BitmapDrawable) {
- return new Pair<>(new AdaptiveOutlineDrawable(
- resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second);
+ return new Pair<>(
+ new AdaptiveOutlineDrawable(
+ resources, ((BitmapDrawable) pair.first).getBitmap()),
+ pair.second);
}
int hashCode;
@@ -198,15 +202,12 @@
hashCode = cachedDevice.getAddress().hashCode();
}
- return new Pair<>(buildBtRainbowDrawable(context,
- pair.first, hashCode), pair.second);
+ return new Pair<>(buildBtRainbowDrawable(context, pair.first, hashCode), pair.second);
}
- /**
- * Build Bluetooth device icon with rainbow
- */
- private static Drawable buildBtRainbowDrawable(Context context, Drawable drawable,
- int hashCode) {
+ /** Build Bluetooth device icon with rainbow */
+ private static Drawable buildBtRainbowDrawable(
+ Context context, Drawable drawable, int hashCode) {
final Resources resources = context.getResources();
// Deal with normal headset
@@ -222,38 +223,37 @@
return adaptiveIcon;
}
- /**
- * Get bluetooth icon with description
- */
- public static Pair<Drawable, String> getBtDrawableWithDescription(Context context,
- CachedBluetoothDevice cachedDevice) {
- final Pair<Drawable, String> pair = BluetoothUtils.getBtClassDrawableWithDescription(
- context, cachedDevice);
+ /** Get bluetooth icon with description */
+ public static Pair<Drawable, String> getBtDrawableWithDescription(
+ Context context, CachedBluetoothDevice cachedDevice) {
+ final Pair<Drawable, String> pair =
+ BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice);
final BluetoothDevice bluetoothDevice = cachedDevice.getDevice();
- final int iconSize = context.getResources().getDimensionPixelSize(
- R.dimen.bt_nearby_icon_size);
+ final int iconSize =
+ context.getResources().getDimensionPixelSize(R.dimen.bt_nearby_icon_size);
final Resources resources = context.getResources();
// Deal with advanced device icon
if (isAdvancedDetailsHeader(bluetoothDevice)) {
- final Uri iconUri = getUriMetaData(bluetoothDevice,
- BluetoothDevice.METADATA_MAIN_ICON);
+ final Uri iconUri = getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON);
if (iconUri != null) {
try {
- context.getContentResolver().takePersistableUriPermission(iconUri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ context.getContentResolver()
+ .takePersistableUriPermission(
+ iconUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
} catch (SecurityException e) {
Log.e(TAG, "Failed to take persistable permission for: " + iconUri, e);
}
try {
- final Bitmap bitmap = MediaStore.Images.Media.getBitmap(
- context.getContentResolver(), iconUri);
+ final Bitmap bitmap =
+ MediaStore.Images.Media.getBitmap(
+ context.getContentResolver(), iconUri);
if (bitmap != null) {
- final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
- iconSize, false);
+ final Bitmap resizedBitmap =
+ Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false);
bitmap.recycle();
- return new Pair<>(new BitmapDrawable(resources,
- resizedBitmap), pair.second);
+ return new Pair<>(
+ new BitmapDrawable(resources, resizedBitmap), pair.second);
}
} catch (IOException e) {
Log.e(TAG, "Failed to get drawable for: " + iconUri, e);
@@ -280,8 +280,8 @@
return true;
}
// The metadata is for Android S
- String deviceType = getStringMetaData(bluetoothDevice,
- BluetoothDevice.METADATA_DEVICE_TYPE);
+ String deviceType =
+ getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)
|| TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH)
|| TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT)
@@ -306,8 +306,8 @@
return true;
}
// The metadata is for Android S
- String deviceType = getStringMetaData(bluetoothDevice,
- BluetoothDevice.METADATA_DEVICE_TYPE);
+ String deviceType =
+ getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) {
Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device ");
return true;
@@ -321,15 +321,15 @@
* @param device Must be one of the public constants in {@link BluetoothClass.Device}
* @return true if device class matches, false otherwise.
*/
- public static boolean isDeviceClassMatched(@NonNull BluetoothDevice bluetoothDevice,
- int device) {
+ public static boolean isDeviceClassMatched(
+ @NonNull BluetoothDevice bluetoothDevice, int device) {
final BluetoothClass bluetoothClass = bluetoothDevice.getBluetoothClass();
return bluetoothClass != null && bluetoothClass.getDeviceClass() == device;
}
private static boolean isAdvancedHeaderEnabled() {
- if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED,
- true)) {
+ if (!DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SETTINGS_UI, BT_ADVANCED_HEADER_ENABLED, true)) {
Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false");
return false;
}
@@ -345,9 +345,7 @@
return false;
}
- /**
- * Create an Icon pointing to a drawable.
- */
+ /** Create an Icon pointing to a drawable. */
public static IconCompat createIconWithDrawable(Drawable drawable) {
Bitmap bitmap;
if (drawable instanceof BitmapDrawable) {
@@ -355,19 +353,15 @@
} else {
final int width = drawable.getIntrinsicWidth();
final int height = drawable.getIntrinsicHeight();
- bitmap = createBitmap(drawable,
- width > 0 ? width : 1,
- height > 0 ? height : 1);
+ bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1);
}
return IconCompat.createWithBitmap(bitmap);
}
- /**
- * Build device icon with advanced outline
- */
+ /** Build device icon with advanced outline */
public static Drawable buildAdvancedDrawable(Context context, Drawable drawable) {
- final int iconSize = context.getResources().getDimensionPixelSize(
- R.dimen.advanced_icon_size);
+ final int iconSize =
+ context.getResources().getDimensionPixelSize(R.dimen.advanced_icon_size);
final Resources resources = context.getResources();
Bitmap bitmap = null;
@@ -376,14 +370,12 @@
} else {
final int width = drawable.getIntrinsicWidth();
final int height = drawable.getIntrinsicHeight();
- bitmap = createBitmap(drawable,
- width > 0 ? width : 1,
- height > 0 ? height : 1);
+ bitmap = createBitmap(drawable, width > 0 ? width : 1, height > 0 ? height : 1);
}
if (bitmap != null) {
- final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, iconSize,
- iconSize, false);
+ final Bitmap resizedBitmap =
+ Bitmap.createScaledBitmap(bitmap, iconSize, iconSize, false);
bitmap.recycle();
return new AdaptiveOutlineDrawable(resources, resizedBitmap, ICON_TYPE_ADVANCED);
}
@@ -391,9 +383,7 @@
return drawable;
}
- /**
- * Creates a drawable with specified width and height.
- */
+ /** Creates a drawable with specified width and height. */
public static Bitmap createBitmap(Drawable drawable, int width, int height) {
final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
@@ -487,11 +477,8 @@
}
/**
- * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means:
- * 1) currently connected
- * 2) is Hearing Aid or LE Audio
- * OR
- * 3) connected profile matches currentAudioProfile
+ * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means: 1) currently
+ * connected 2) is Hearing Aid or LE Audio OR 3) connected profile matches currentAudioProfile
*
* @param cachedDevice the CachedBluetoothDevice
* @param audioManager audio manager to get the current audio profile
@@ -519,8 +506,11 @@
// It would show in Available Devices group.
if (cachedDevice.isConnectedAshaHearingAidDevice()
|| cachedDevice.isConnectedLeAudioDevice()) {
- Log.d(TAG, "isFilterMatched() device : "
- + cachedDevice.getName() + ", the profile is connected.");
+ Log.d(
+ TAG,
+ "isFilterMatched() device : "
+ + cachedDevice.getName()
+ + ", the profile is connected.");
return true;
}
// According to the current audio profile type,
@@ -541,11 +531,79 @@
return isFilterMatched;
}
+ /** Returns if the le audio sharing is enabled. */
+ public static boolean isAudioSharingEnabled() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ return Flags.enableLeAudioSharing()
+ && adapter.isLeAudioBroadcastSourceSupported()
+ == BluetoothStatusCodes.FEATURE_SUPPORTED
+ && adapter.isLeAudioBroadcastAssistantSupported()
+ == BluetoothStatusCodes.FEATURE_SUPPORTED;
+ }
+
+ /** Returns if the broadcast is on-going. */
+ @WorkerThread
+ public static boolean isBroadcasting(@Nullable LocalBluetoothManager manager) {
+ if (manager == null) return false;
+ LocalBluetoothLeBroadcast broadcast =
+ manager.getProfileManager().getLeAudioBroadcastProfile();
+ return broadcast != null && broadcast.isEnabled(null);
+ }
+
/**
- * Checks if the Bluetooth device is an available hearing device, which means:
- * 1) currently connected
- * 2) is Hearing Aid
- * 3) connected profile match hearing aid related profiles (e.g. ASHA, HAP)
+ * Check if {@link CachedBluetoothDevice} has connected to a broadcast source.
+ *
+ * @param cachedDevice The cached bluetooth device to check.
+ * @param localBtManager The BT manager to provide BT functions.
+ * @return Whether the device has connected to a broadcast source.
+ */
+ @WorkerThread
+ public static boolean hasConnectedBroadcastSource(
+ CachedBluetoothDevice cachedDevice, LocalBluetoothManager localBtManager) {
+ if (localBtManager == null) {
+ Log.d(TAG, "Skip check hasConnectedBroadcastSource due to bt manager is null");
+ return false;
+ }
+ LocalBluetoothLeBroadcastAssistant assistant =
+ localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "Skip check hasConnectedBroadcastSource due to assistant profile is null");
+ return false;
+ }
+ List<BluetoothLeBroadcastReceiveState> sourceList =
+ assistant.getAllSources(cachedDevice.getDevice());
+ if (!sourceList.isEmpty() && sourceList.stream().anyMatch(BluetoothUtils::isConnected)) {
+ Log.d(
+ TAG,
+ "Lead device has connected broadcast source, device = "
+ + cachedDevice.getDevice().getAnonymizedAddress());
+ return true;
+ }
+ // Return true if member device is in broadcast.
+ for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) {
+ List<BluetoothLeBroadcastReceiveState> list =
+ assistant.getAllSources(device.getDevice());
+ if (!list.isEmpty() && list.stream().anyMatch(BluetoothUtils::isConnected)) {
+ Log.d(
+ TAG,
+ "Member device has connected broadcast source, device = "
+ + device.getDevice().getAnonymizedAddress());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Checks the connectivity status based on the provided broadcast receive state. */
+ @WorkerThread
+ public static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
+ return state.getBisSyncState().stream().anyMatch(bitmap -> bitmap != 0);
+ }
+
+ /**
+ * Checks if the Bluetooth device is an available hearing device, which means: 1) currently
+ * connected 2) is Hearing Aid 3) connected profile match hearing aid related profiles (e.g.
+ * ASHA, HAP)
*
* @param cachedDevice the CachedBluetoothDevice
* @return if the device is Available hearing device
@@ -553,19 +611,20 @@
@WorkerThread
public static boolean isAvailableHearingDevice(CachedBluetoothDevice cachedDevice) {
if (isDeviceConnected(cachedDevice) && cachedDevice.isConnectedHearingAidDevice()) {
- Log.d(TAG, "isFilterMatched() device : "
- + cachedDevice.getName() + ", the profile is connected.");
+ Log.d(
+ TAG,
+ "isFilterMatched() device : "
+ + cachedDevice.getName()
+ + ", the profile is connected.");
return true;
}
return false;
}
/**
- * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means:
- * 1) currently connected
- * 2) is not Hearing Aid or LE Audio
- * AND
- * 3) connected profile does not match currentAudioProfile
+ * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means: 1) currently
+ * connected 2) is not Hearing Aid or LE Audio AND 3) connected profile does not match
+ * currentAudioProfile
*
* @param cachedDevice the CachedBluetoothDevice
* @param audioManager audio manager to get the current audio profile
@@ -675,29 +734,28 @@
}
/**
- * Returns the BluetoothDevice's exclusive manager
- * ({@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the
- * given set, otherwise null.
+ * Returns the BluetoothDevice's exclusive manager ({@link
+ * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given
+ * set, otherwise null.
*/
@Nullable
private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) {
- byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata(
- BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
+ byte[] exclusiveManagerNameBytes =
+ bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
if (exclusiveManagerNameBytes == null) {
- Log.d(TAG, "Bluetooth device " + bluetoothDevice.getName()
- + " doesn't have exclusive manager");
+ Log.d(
+ TAG,
+ "Bluetooth device "
+ + bluetoothDevice.getName()
+ + " doesn't have exclusive manager");
return null;
}
String exclusiveManagerName = new String(exclusiveManagerNameBytes);
- return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName
- : null;
+ return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null;
}
- /**
- * Checks if given package is installed
- */
- private static boolean isPackageInstalled(Context context,
- String packageName) {
+ /** Checks if given package is installed */
+ private static boolean isPackageInstalled(Context context, String packageName) {
PackageManager packageManager = context.getPackageManager();
try {
packageManager.getPackageInfo(packageName, 0);
@@ -709,13 +767,12 @@
}
/**
- * A BluetoothDevice is exclusively managed if
- * 1) it has field {@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata.
- * 2) the exclusive manager app name is in the allowlist.
- * 3) the exclusive manager app is installed.
+ * A BluetoothDevice is exclusively managed if 1) it has field {@link
+ * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is
+ * in the allowlist. 3) the exclusive manager app is installed.
*/
- public static boolean isExclusivelyManagedBluetoothDevice(@NonNull Context context,
- @NonNull BluetoothDevice bluetoothDevice) {
+ public static boolean isExclusivelyManagedBluetoothDevice(
+ @NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) {
String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice);
if (exclusiveManagerName == null) {
return false;
@@ -728,9 +785,7 @@
}
}
- /**
- * Return the allowlist for exclusive manager names.
- */
+ /** Return the allowlist for exclusive manager names. */
@NonNull
public static Set<String> getExclusiveManagers() {
return EXCLUSIVE_MANAGERS;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 4e6d3cb..9b1e4b7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -254,8 +254,6 @@
protected abstract List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName);
protected final void rebuildDeviceList() {
- mMediaDevices.clear();
- mCurrentConnectedDevice = null;
buildAvailableRoutes();
}
@@ -524,6 +522,7 @@
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
private synchronized void buildAvailableRoutes() {
+ mMediaDevices.clear();
RoutingSessionInfo activeSession = getActiveRoutingSession();
for (MediaRoute2Info route : getAvailableRoutes(activeSession)) {
@@ -533,6 +532,12 @@
}
addMediaDevice(route, activeSession);
}
+
+ // In practice, mMediaDevices should always have at least one route.
+ if (!mMediaDevices.isEmpty()) {
+ // First device on the list is always the first selected route.
+ mCurrentConnectedDevice = mMediaDevices.get(0);
+ }
}
private synchronized List<MediaRoute2Info> getAvailableRoutes(
@@ -643,9 +648,6 @@
if (mediaDevice != null) {
if (activeSession.getSelectedRoutes().contains(route.getId())) {
mediaDevice.setState(STATE_SELECTED);
- if (mCurrentConnectedDevice == null) {
- mCurrentConnectedDevice = mediaDevice;
- }
}
mMediaDevices.add(mediaDevice);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 1246fd8..f197f9e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
@@ -25,6 +26,7 @@
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -44,6 +46,9 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import java.util.ArrayList;
+import java.util.List;
+
@RunWith(RobolectricTestRunner.class)
public class BluetoothUtilsTest {
@@ -55,6 +60,16 @@
private AudioManager mAudioManager;
@Mock
private PackageManager mPackageManager;
+ @Mock
+ private LocalBluetoothLeBroadcast mBroadcast;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock
+ private LocalBluetoothLeBroadcastAssistant mAssistant;
+ @Mock
+ private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
private Context mContext;
private static final String STRING_METADATA = "string_metadata";
@@ -72,6 +87,9 @@
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
+ when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
}
@Test
@@ -432,6 +450,30 @@
}
@Test
+ public void testIsBroadcasting_broadcastEnabled_returnTrue() {
+ when(mBroadcast.isEnabled(any())).thenReturn(true);
+ assertThat(BluetoothUtils.isBroadcasting(mLocalBluetoothManager)).isEqualTo(true);
+ }
+
+ @Test
+ public void testHasConnectedBroadcastSource_deviceConnectedToBroadcastSource() {
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+
+ List<Long> bisSyncState = new ArrayList<>();
+ bisSyncState.add(1L);
+ when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(any())).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isEqualTo(true);
+ }
+
+ @Test
public void isAvailableHearingDevice_isConnectedHearingAid_returnTure() {
when(mCachedBluetoothDevice.isConnectedHearingAidDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index f8dcec7..d793867 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -110,6 +110,17 @@
.setAddress("00:00:00:00:00:00")
.build();
+ private static final RoutingSessionInfo TEST_REMOTE_ROUTING_SESSION =
+ new RoutingSessionInfo.Builder("FAKE_REMOTE_ROUTING_SESSION_ID", TEST_PACKAGE_NAME)
+ .addSelectedRoute(TEST_ID_1)
+ .build();
+
+ private static final MediaRoute2Info TEST_REMOTE_ROUTE =
+ new MediaRoute2Info.Builder(TEST_ID_1, "REMOTE_ROUTE")
+ .setSystemRoute(true)
+ .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO)
+ .build();
+
@Mock
private MediaRouter2Manager mRouterManager;
@Mock
@@ -151,7 +162,10 @@
RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
mInfoMediaManager.mRouterManager = mRouterManager;
// Since test is running in Robolectric, return a fake session to avoid NPE.
- when(mRouterManager.getRoutingSessions(anyString())).thenReturn(List.of(sessionInfo));
+ when(mRouterManager.getRoutingSessions(anyString()))
+ .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION));
+ when(mRouterManager.getSelectedRoutes(any()))
+ .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE));
mInfoMediaManager.startScan();
mInfoMediaManager.stopScan();
@@ -191,52 +205,27 @@
@Test
public void onSessionReleased_shouldUpdateConnectedDevice() {
- final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
- final RoutingSessionInfo sessionInfo1 = mock(RoutingSessionInfo.class);
- routingSessionInfos.add(sessionInfo1);
- final RoutingSessionInfo sessionInfo2 = mock(RoutingSessionInfo.class);
- routingSessionInfos.add(sessionInfo2);
+ mInfoMediaManager.mRouterManager = mRouterManager;
- final List<String> selectedRoutesSession1 = new ArrayList<>();
- selectedRoutesSession1.add(TEST_ID_1);
- when(sessionInfo1.getSelectedRoutes()).thenReturn(selectedRoutesSession1);
-
- final List<String> selectedRoutesSession2 = new ArrayList<>();
- selectedRoutesSession2.add(TEST_ID_2);
- when(sessionInfo2.getSelectedRoutes()).thenReturn(selectedRoutesSession2);
-
- mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
-
- final MediaRoute2Info info1 = mock(MediaRoute2Info.class);
- when(info1.getId()).thenReturn(TEST_ID_1);
- when(info1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
- final MediaRoute2Info info2 = mock(MediaRoute2Info.class);
- when(info2.getId()).thenReturn(TEST_ID_2);
- when(info2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info1);
- routes.add(info2);
- mShadowRouter2Manager.setAllRoutes(routes);
- mShadowRouter2Manager.setTransferableRoutes(routes);
-
- final MediaDevice mediaDevice1 = mInfoMediaManager.findMediaDevice(TEST_ID_1);
- assertThat(mediaDevice1).isNull();
- final MediaDevice mediaDevice2 = mInfoMediaManager.findMediaDevice(TEST_ID_2);
- assertThat(mediaDevice2).isNull();
+ // Active routing session is last one in list.
+ when(mRouterManager.getRoutingSessions(anyString()))
+ .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION, TEST_REMOTE_ROUTING_SESSION));
+ when(mRouterManager.getSelectedRoutes(TEST_SYSTEM_ROUTING_SESSION))
+ .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE));
+ when(mRouterManager.getSelectedRoutes(TEST_REMOTE_ROUTING_SESSION))
+ .thenReturn(List.of(TEST_REMOTE_ROUTE));
mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
- final MediaDevice infoDevice1 = mInfoMediaManager.mMediaDevices.get(0);
- assertThat(infoDevice1.getId()).isEqualTo(TEST_ID_1);
- final MediaDevice infoDevice2 = mInfoMediaManager.mMediaDevices.get(1);
- assertThat(infoDevice2.getId()).isEqualTo(TEST_ID_2);
- // The active routing session is the last one in the list, which maps to infoDevice2.
- assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice2);
+ MediaDevice remoteDevice = mInfoMediaManager.findMediaDevice(TEST_REMOTE_ROUTE.getId());
+ assertThat(remoteDevice).isNotNull();
+ assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(remoteDevice);
- routingSessionInfos.remove(sessionInfo2);
- mInfoMediaManager.mMediaRouterCallback.onSessionReleased(sessionInfo2);
- assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice1);
+ when(mRouterManager.getRoutingSessions(anyString()))
+ .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION));
+ mInfoMediaManager.mMediaRouterCallback.onSessionReleased(TEST_REMOTE_ROUTING_SESSION);
+ MediaDevice systemRoute = mInfoMediaManager.findMediaDevice(TEST_SYSTEM_ROUTE_ID);
+ assertThat(systemRoute).isNotNull();
+ assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(systemRoute);
}
@Test
@@ -794,18 +783,16 @@
@Test
public void onSessionUpdated_shouldDispatchDeviceListAdded() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(info.isSystemRoute()).thenReturn(true);
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- mShadowRouter2Manager.setAllRoutes(routes);
+ mInfoMediaManager.mRouterManager = mRouterManager;
+ // Since test is running in Robolectric, return a fake session to avoid NPE.
+ when(mRouterManager.getRoutingSessions(anyString()))
+ .thenReturn(List.of(TEST_SYSTEM_ROUTING_SESSION));
+ when(mRouterManager.getSelectedRoutes(any()))
+ .thenReturn(List.of(TEST_SELECTED_SYSTEM_ROUTE));
mInfoMediaManager.registerCallback(mCallback);
- mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(mock(RoutingSessionInfo.class));
+ mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(TEST_SYSTEM_ROUTING_SESSION);
verify(mCallback).onDeviceListAdded(any());
}
@@ -871,7 +858,7 @@
}
@Test
- public void addMediaDevice_deviceIncludedInSelectedDevices_shouldSetAsCurrentConnected() {
+ public void onRoutesUpdated_setsFirstSelectedRouteAsCurrentConnectedDevice() {
final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
mock(CachedBluetoothDeviceManager.class);
@@ -886,14 +873,14 @@
when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME))
.thenReturn(List.of(selectedBtSession));
+ when(mRouterManager.getSelectedRoutes(any())).thenReturn(List.of(TEST_BLUETOOTH_ROUTE));
when(mLocalBluetoothManager.getCachedDeviceManager())
.thenReturn(cachedBluetoothDeviceManager);
when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class)))
.thenReturn(cachedDevice);
mInfoMediaManager.mRouterManager = mRouterManager;
- mInfoMediaManager.mMediaDevices.clear();
- mInfoMediaManager.addMediaDevice(TEST_BLUETOOTH_ROUTE, selectedBtSession);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
MediaDevice device = mInfoMediaManager.mMediaDevices.get(0);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index a28cfeb..68167e1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -1127,7 +1127,7 @@
Slog.i(LOG_TAG, "[PERSIST END]");
}
} catch (Throwable t) {
- Slog.wtf(LOG_TAG, "Failed to write settings, restoring old file", t);
+ Slog.e(LOG_TAG, "Failed to write settings, restoring old file", t);
if (t instanceof IOException) {
if (t.getMessage().contains("Couldn't create directory")) {
if (DEBUG) {
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 42952de..5ac0e44 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -50,7 +50,6 @@
import android.os.Binder;
import android.os.BugreportManager;
import android.os.BugreportManager.BugreportCallback;
-import android.os.BugreportManager.BugreportCallback.BugreportErrorCode;
import android.os.BugreportParams;
import android.os.Bundle;
import android.os.FileUtils;
@@ -169,6 +168,8 @@
static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
static final String EXTRA_INFO = "android.intent.extra.INFO";
+ static final String EXTRA_EXTRA_ATTACHMENT_URI =
+ "android.intent.extra.EXTRA_ATTACHMENT_URI";
private static final int MSG_SERVICE_COMMAND = 1;
private static final int MSG_DELAYED_SCREENSHOT = 2;
@@ -634,9 +635,10 @@
long nonce = intent.getLongExtra(EXTRA_BUGREPORT_NONCE, 0);
String baseName = getBugreportBaseName(bugreportType);
String name = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());
+ Uri extraAttachment = intent.getParcelableExtra(EXTRA_EXTRA_ATTACHMENT_URI, Uri.class);
- BugreportInfo info = new BugreportInfo(mContext, baseName, name,
- shareTitle, shareDescription, bugreportType, mBugreportsDir, nonce);
+ BugreportInfo info = new BugreportInfo(mContext, baseName, name, shareTitle,
+ shareDescription, bugreportType, mBugreportsDir, nonce, extraAttachment);
synchronized (mLock) {
if (info.bugreportFile.exists()) {
Log.e(TAG, "Failed to start bugreport generation, the requested bugreport file "
@@ -1184,6 +1186,10 @@
clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
attachments.add(screenshotUri);
}
+ if (info.extraAttachment != null) {
+ clipData.addItem(new ClipData.Item(null, null, null, info.extraAttachment));
+ attachments.add(info.extraAttachment);
+ }
intent.setClipData(clipData);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
@@ -2042,6 +2048,9 @@
*/
final long nonce;
+ @Nullable
+ public Uri extraAttachment = null;
+
private final Object mLock = new Object();
/**
@@ -2049,7 +2058,8 @@
*/
BugreportInfo(Context context, String baseName, String name,
@Nullable String shareTitle, @Nullable String shareDescription,
- @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce) {
+ @BugreportParams.BugreportMode int type, File bugreportsDir, long nonce,
+ @Nullable Uri extraAttachment) {
this.context = context;
this.name = this.initialName = name;
this.shareTitle = shareTitle == null ? "" : shareTitle;
@@ -2058,6 +2068,7 @@
this.nonce = nonce;
this.baseName = baseName;
this.bugreportFile = new File(bugreportsDir, getFileName(this, ".zip"));
+ this.extraAttachment = extraAttachment;
}
void createBugreportFile() {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 112d964..6810aac9 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -26,6 +26,14 @@
}
flag {
+ name: "refactor_keyguard_dismiss_intent"
+ namespace: "systemui"
+ description: "Update how keyguard dismiss intents are stored."
+ bug: "275069969"
+}
+
+flag {
+
name: "notification_heads_up_cycling"
namespace: "systemui"
description: "Heads-up notification cycling animation for the Notification Avalanche feature."
@@ -716,3 +724,10 @@
" Compose for the UI."
bug: "325099249"
}
+
+flag {
+ name: "keyboard_docking_indicator"
+ namespace: "systemui"
+ description: "Glow bar indicator reveals upon keyboard docking."
+ bug: "324600132"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index ed80277..32c0313 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -17,9 +17,11 @@
package com.android.systemui.communal.ui.compose
import android.appwidget.AppWidgetHostView
+import android.content.res.Configuration
import android.graphics.drawable.Icon
import android.os.Bundle
import android.util.SizeF
+import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
@@ -92,6 +94,7 @@
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
@@ -110,8 +113,6 @@
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
import androidx.window.layout.WindowMetricsCalculator
-import com.android.compose.modifiers.height
-import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
@@ -364,7 +365,7 @@
liveContentKeys.indexOfFirst { !prevLiveContentKeys.contains(it) }
// Scroll if current position is behind the first updated content
- if (indexOfFirstUpdatedContent in 0..<gridState.firstVisibleItemIndex) {
+ if (indexOfFirstUpdatedContent in 0 until gridState.firstVisibleItemIndex) {
// Launching with a scope to prevent the job from being canceled in the case of a
// recomposition during scrolling
coroutineScope.launch { gridState.animateScrollToItem(indexOfFirstUpdatedContent) }
@@ -834,6 +835,13 @@
widgetConfigurator: WidgetConfigurator?,
modifier: Modifier = Modifier,
) {
+ var widgetId: Int by remember { mutableStateOf(-1) }
+ var configuration: Configuration? by remember { mutableStateOf(null) }
+
+ // In addition to returning the current configuration, this also causes a recompose on
+ // configuration change.
+ val currentConfiguration = LocalConfiguration.current
+
Box(
modifier =
modifier.thenIf(!viewModel.isEditMode && model.inQuietMode) {
@@ -849,18 +857,48 @@
AndroidView(
modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
factory = { context ->
- model.appWidgetHost
- .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
- .apply {
- updateAppWidgetSize(Bundle.EMPTY, listOf(size))
- // Remove the extra padding applied to AppWidgetHostView to allow widgets to
- // occupy the entire box.
- setPadding(0)
- }
+ // This FrameLayout becomes the container view for the AppWidgetHostView we add as a
+ // child in update below. Having a container view allows for updating the contained
+ // host view as needed (e.g. on configuration changes).
+ FrameLayout(context).apply {
+ layoutParams =
+ ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ setPadding(0)
+ }
+ },
+ update = { widgetContainer: ViewGroup ->
+ if (widgetId == model.appWidgetId && currentConfiguration.equals(configuration)) {
+ return@AndroidView
+ }
+
+ // Add the widget's host view to the FrameLayout parent (after removing any
+ // previously added host view).
+ widgetContainer.removeAllViews()
+ widgetContainer.addView(
+ model.appWidgetHost
+ .createViewForCommunal(
+ widgetContainer.context,
+ model.appWidgetId,
+ model.providerInfo
+ )
+ .apply {
+ updateAppWidgetSize(Bundle.EMPTY, listOf(size))
+ // Remove the extra padding applied to AppWidgetHostView to allow
+ // widgets to occupy the entire box.
+ setPadding(0)
+ }
+ )
+
+ widgetId = model.appWidgetId
+ configuration = currentConfiguration
},
// For reusing composition in lazy lists.
onReset = {},
)
+
if (
viewModel is CommunalEditModeViewModel &&
model.reconfigurable &&
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index c008a1a..28e92aa 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -86,16 +86,21 @@
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
- Modifier.fillMaxWidth(0.5f)
- .fillMaxHeight()
- .align(alignment = Alignment.TopEnd)
+ burnInParams = null,
+ modifier =
+ Modifier.fillMaxWidth(0.5f)
+ .fillMaxHeight()
+ .align(alignment = Alignment.TopEnd)
)
}
}
}
if (!shouldUseSplitNotificationShade) {
with(notificationSection) {
- Notifications(Modifier.weight(weight = 1f))
+ Notifications(
+ burnInParams = null,
+ modifier = Modifier.weight(weight = 1f)
+ )
}
}
if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 091a439..b8f00dc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -86,16 +86,21 @@
if (shouldUseSplitNotificationShade) {
with(notificationSection) {
Notifications(
- Modifier.fillMaxWidth(0.5f)
- .fillMaxHeight()
- .align(alignment = Alignment.TopEnd)
+ burnInParams = null,
+ modifier =
+ Modifier.fillMaxWidth(0.5f)
+ .fillMaxHeight()
+ .align(alignment = Alignment.TopEnd)
)
}
}
}
if (!shouldUseSplitNotificationShade) {
with(notificationSection) {
- Notifications(Modifier.weight(weight = 1f))
+ Notifications(
+ burnInParams = null,
+ modifier = Modifier.weight(weight = 1f)
+ )
}
}
if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
index 09d76a3..cba5453 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt
@@ -149,6 +149,7 @@
if (areNotificationsVisible) {
with(notificationSection) {
Notifications(
+ burnInParams = burnIn.parameters,
modifier = Modifier.fillMaxWidth().weight(weight = 1f)
)
}
@@ -375,6 +376,7 @@
)
}
Notifications(
+ burnInParams = burnIn.parameters,
modifier =
Modifier.fillMaxHeight()
.weight(weight = 1f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
index 51a7e8e..eb389e6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt
@@ -74,6 +74,7 @@
modifier =
modifier
.height(dimensionResource(R.dimen.small_clock_height))
+ .padding(horizontal = dimensionResource(R.dimen.clock_padding_start))
.padding(top = { viewModel.getSmallClockTopMargin(context) })
.onTopPlacementChanged(onTopChanged)
.burnInAware(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index a31b533..f48fa88 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -32,6 +32,9 @@
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.MigrateClocksToBlueprint
+import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.notifications.ui.composable.ConstrainedNotificationStack
import com.android.systemui.res.R
@@ -48,6 +51,7 @@
@Inject
constructor(
private val viewModel: NotificationsPlaceholderViewModel,
+ private val aodBurnInViewModel: AodBurnInViewModel,
sharedNotificationContainer: SharedNotificationContainer,
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
stackScrollLayout: NotificationStackScrollLayout,
@@ -77,8 +81,12 @@
)
}
+ /**
+ * @param burnInParams params to make this view adaptive to burn-in, `null` to disable burn-in
+ * adjustment
+ */
@Composable
- fun SceneScope.Notifications(modifier: Modifier = Modifier) {
+ fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) {
val shouldUseSplitNotificationShade by
lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsState()
val areNotificationsVisible by
@@ -97,9 +105,21 @@
ConstrainedNotificationStack(
viewModel = viewModel,
modifier =
- modifier.fillMaxWidth().thenIf(shouldUseSplitNotificationShade) {
- Modifier.padding(top = splitShadeTopMargin)
- },
+ modifier
+ .fillMaxWidth()
+ .thenIf(shouldUseSplitNotificationShade) {
+ Modifier.padding(top = splitShadeTopMargin)
+ }
+ .let {
+ if (burnInParams == null) {
+ it
+ } else {
+ it.burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ )
+ }
+ },
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
new file mode 100644
index 0000000..ca6b343
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.ui.composable
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.width
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+
+@Composable
+fun BrightnessMirror(
+ viewModel: BrightnessMirrorViewModel,
+ qsSceneAdapter: QSSceneAdapter,
+ modifier: Modifier = Modifier,
+) {
+ val isShowing by viewModel.isShowing.collectAsState()
+ val mirrorAlpha by
+ animateFloatAsState(
+ targetValue = if (isShowing) 1f else 0f,
+ label = "alphaAnimationBrightnessMirrorShowing",
+ )
+ val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsState()
+ val offset = IntOffset(0, mirrorOffsetAndSize.yOffset)
+
+ Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = mirrorAlpha }) {
+ QuickSettingsTheme {
+ // The assumption for using this AndroidView is that there will be only one in view at
+ // a given time (which is a reasonable assumption). Because `QSSceneAdapter` (actually
+ // `BrightnessSliderController` only supports a single mirror).
+ // The benefit of doing it like this is that if the configuration changes or QSImpl is
+ // re-inflated, it's not relevant to the composable, as we'll always get a new one.
+ AndroidView(
+ modifier =
+ Modifier.align(Alignment.TopCenter)
+ .offset { offset }
+ .width { mirrorOffsetAndSize.width }
+ .height { mirrorOffsetAndSize.height },
+ factory = { context ->
+ val (view, controller) =
+ BrightnessMirrorInflater.inflate(context, viewModel.sliderControllerFactory)
+ viewModel.setToggleSlider(controller)
+ view
+ },
+ update = { qsSceneAdapter.setBrightnessMirrorController(viewModel) },
+ onRelease = { qsSceneAdapter.setBrightnessMirrorController(null) }
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 5b9213a..a376834 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -17,7 +17,9 @@
package com.android.systemui.qs.ui.composable
import android.view.ViewGroup
+import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
@@ -48,6 +50,7 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.colorResource
@@ -119,9 +122,28 @@
statusBarIconController: StatusBarIconController,
modifier: Modifier = Modifier,
) {
+ val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+ val contentAlpha by
+ animateFloatAsState(
+ targetValue = if (brightnessMirrorShowing) 0f else 1f,
+ label = "alphaAnimationBrightnessMirrorContentHiding",
+ )
+
+ BrightnessMirror(
+ viewModel = viewModel.brightnessMirrorViewModel,
+ qsSceneAdapter = viewModel.qsSceneAdapter
+ )
+
// TODO(b/280887232): implement the real UI.
- Box(modifier = modifier.fillMaxSize()) {
+ Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = contentAlpha }) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
+
+ BackHandler(
+ enabled = isCustomizing,
+ ) {
+ viewModel.qsSceneAdapter.requestCloseCustomizer()
+ }
+
val collapsedHeaderHeight =
with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
val lifecycleOwner = LocalLifecycleOwner.current
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index fcd7760..02a12e4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -54,9 +54,9 @@
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.TransitionState
import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.animateElementFloatAsState
-import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.settingslib.Utils
import com.android.systemui.battery.BatteryMeterView
@@ -87,10 +87,6 @@
val ShadeCarrierGroup = ElementKey("ShadeCarrierGroup")
}
- object Keys {
- val transitionProgress = ValueKey("ShadeHeaderTransitionProgress")
- }
-
object Values {
val ClockScale = ValueKey("ShadeHeaderClockScale")
}
@@ -119,19 +115,17 @@
return
}
- val formatProgress =
- animateSceneFloatAsState(0f, ShadeHeader.Keys.transitionProgress)
- .unsafeCompositionState(initialValue = 0f)
-
val cutoutWidth = LocalDisplayCutout.current.width()
val cutoutLocation = LocalDisplayCutout.current.location
val useExpandedFormat by
- remember(formatProgress) {
+ remember(cutoutLocation) {
derivedStateOf {
- cutoutLocation != CutoutLocation.CENTER || formatProgress.value > 0.5f
+ cutoutLocation != CutoutLocation.CENTER ||
+ shouldUseExpandedFormat(layoutState.transitionState)
}
}
+
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
// This layout assumes it is globally positioned at (0, 0) and is the
@@ -207,7 +201,7 @@
val screenWidth = constraints.maxWidth
val cutoutWidthPx = cutoutWidth.roundToPx()
- val height = ShadeHeader.Dimensions.CollapsedHeight.roundToPx()
+ val height = CollapsedHeight.roundToPx()
val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height)
val startMeasurable = measurables[0][0]
@@ -261,11 +255,10 @@
return
}
- val formatProgress =
- animateSceneFloatAsState(1f, ShadeHeader.Keys.transitionProgress)
- .unsafeCompositionState(initialValue = 1f)
- val useExpandedFormat by
- remember(formatProgress) { derivedStateOf { formatProgress.value > 0.5f } }
+ val useExpandedFormat by remember {
+ derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) }
+ }
+
val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
Box(modifier = modifier) {
@@ -530,3 +523,15 @@
modifier = modifier.element(ShadeHeader.Elements.PrivacyChip),
)
}
+
+private fun shouldUseExpandedFormat(state: TransitionState): Boolean {
+ return when (state) {
+ is TransitionState.Idle -> {
+ state.currentScene == Scenes.QuickSettings
+ }
+ is TransitionState.Transition -> {
+ (state.isTransitioning(Scenes.Shade, Scenes.QuickSettings) && state.progress >= 0.5) ||
+ (state.isTransitioning(Scenes.QuickSettings, Scenes.Shade) && state.progress < 0.5)
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 85798ac..9bd6f81 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.ui.composable
import android.view.ViewGroup
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.clipScrollableContainer
@@ -33,6 +34,7 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -45,6 +47,7 @@
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalDensity
@@ -70,6 +73,7 @@
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
+import com.android.systemui.qs.ui.composable.BrightnessMirror
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
@@ -302,12 +306,25 @@
}
}
+ val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState()
+ val contentAlpha by
+ animateFloatAsState(
+ targetValue = if (brightnessMirrorShowing) 0f else 1f,
+ label = "alphaAnimationBrightnessMirrorContentHiding",
+ )
+
+ val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
+
Box(
modifier =
modifier
.fillMaxSize()
.element(Shade.Elements.BackgroundScrim)
- .background(colorResource(R.color.shade_scrim_background_dark))
+ // Cannot set the alpha of the whole element to 0, because the mirror should be
+ // in the QS column.
+ .background(
+ colorResource(R.color.shade_scrim_background_dark).copy(alpha = contentAlpha)
+ )
) {
Column(
modifier = Modifier.fillMaxSize(),
@@ -317,61 +334,80 @@
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
- modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
+ modifier =
+ Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
+ .then(brightnessMirrorShowingModifier)
)
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
- Column(
- verticalArrangement = Arrangement.Top,
- modifier =
- Modifier.weight(1f).fillMaxSize().thenIf(!isCustomizing) {
- Modifier.padding(bottom = navBarBottomHeight)
- },
- ) {
+ Box(modifier = Modifier.weight(1f)) {
+ BrightnessMirror(
+ viewModel = viewModel.brightnessMirrorViewModel,
+ qsSceneAdapter = viewModel.qsSceneAdapter,
+ // Need to remove the offset of the header height, as the mirror uses
+ // the position of the Brightness slider in the window
+ modifier = Modifier.offset(y = -ShadeHeader.Dimensions.CollapsedHeight)
+ )
Column(
+ verticalArrangement = Arrangement.Top,
modifier =
- Modifier.fillMaxSize().weight(1f).thenIf(!isCustomizing) {
- Modifier.verticalNestedScrollToScene()
- .verticalScroll(
- quickSettingsScrollState,
- enabled = isScrollable
- )
- .clipScrollableContainer(Orientation.Horizontal)
- }
+ Modifier.fillMaxSize().thenIf(!isCustomizing) {
+ Modifier.padding(bottom = navBarBottomHeight)
+ },
) {
- Box(
+ Column(
modifier =
- Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings)
+ Modifier.fillMaxSize()
+ .weight(1f)
+ .thenIf(!isCustomizing) {
+ Modifier.verticalNestedScrollToScene()
+ .verticalScroll(
+ quickSettingsScrollState,
+ enabled = isScrollable
+ )
+ .clipScrollableContainer(Orientation.Horizontal)
+ }
+ .then(brightnessMirrorShowingModifier)
) {
- QuickSettings(
- qsSceneAdapter = viewModel.qsSceneAdapter,
- heightProvider = { viewModel.qsSceneAdapter.qsHeight },
- isSplitShade = true,
+ Box(
+ modifier =
+ Modifier.element(QuickSettings.Elements.SplitShadeQuickSettings)
+ ) {
+ QuickSettings(
+ qsSceneAdapter = viewModel.qsSceneAdapter,
+ heightProvider = { viewModel.qsSceneAdapter.qsHeight },
+ isSplitShade = true,
+ modifier = Modifier.fillMaxWidth(),
+ squishiness = tileSquishiness,
+ )
+ }
+
+ MediaIfVisible(
+ viewModel = viewModel,
+ mediaCarouselController = mediaCarouselController,
+ mediaHost = mediaHost,
modifier = Modifier.fillMaxWidth(),
- squishiness = tileSquishiness,
)
}
-
- MediaIfVisible(
- viewModel = viewModel,
- mediaCarouselController = mediaCarouselController,
- mediaHost = mediaHost,
- modifier = Modifier.fillMaxWidth(),
+ FooterActionsWithAnimatedVisibility(
+ viewModel = footerActionsViewModel,
+ isCustomizing = isCustomizing,
+ lifecycleOwner = lifecycleOwner,
+ modifier =
+ Modifier.align(Alignment.CenterHorizontally)
+ .then(brightnessMirrorShowingModifier),
)
}
- FooterActionsWithAnimatedVisibility(
- viewModel = footerActionsViewModel,
- isCustomizing = isCustomizing,
- lifecycleOwner = lifecycleOwner,
- modifier = Modifier.align(Alignment.CenterHorizontally),
- )
}
NotificationScrollingStack(
viewModel = viewModel.notifications,
maxScrimTop = { 0f },
modifier =
- Modifier.weight(1f).fillMaxHeight().padding(bottom = navBarBottomHeight),
+ Modifier.weight(1f)
+ .fillMaxHeight()
+ .padding(bottom = navBarBottomHeight)
+ .then(brightnessMirrorShowingModifier),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index b1fbe35..9f0da00 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.view.ContextThemeWrapper
import android.view.View
+import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -55,6 +56,7 @@
@Composable
private fun Title() {
Text(
+ modifier = Modifier.basicMarquee(),
text = stringResource(R.string.volume_panel_noise_control_title),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt
new file mode 100644
index 0000000..167eb65
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/BottomComponentButtonSurface.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.button.ui.composable
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+/**
+ * Container to create a rim around the button. Both `Expandable` and `OutlinedIconToggleButton`
+ * have antialiasing problem when used with [androidx.compose.foundation.BorderStroke] and some
+ * contrast container color.
+ */
+// TODO(b/331584069): Remove this once antialiasing bug is fixed
+@Composable
+fun BottomComponentButtonSurface(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
+ Surface(
+ modifier = modifier.height(64.dp),
+ shape = RoundedCornerShape(28.dp),
+ color = MaterialTheme.colorScheme.surface,
+ content = content,
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index b721e41..fc511e1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -16,13 +16,12 @@
package com.android.systemui.volume.panel.component.button.ui.composable
-import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
@@ -37,7 +36,6 @@
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.compose.animation.Expandable
import com.android.systemui.animation.Expandable
@@ -64,28 +62,28 @@
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- Expandable(
- modifier =
- Modifier.height(64.dp).fillMaxWidth().semantics {
- role = Role.Button
- contentDescription = label
- },
- color = MaterialTheme.colorScheme.primaryContainer,
- shape = RoundedCornerShape(28.dp),
- contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
- borderStroke = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
- onClick = onClick,
- ) {
- Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ BottomComponentButtonSurface {
+ Expandable(
+ modifier =
+ Modifier.fillMaxSize().padding(8.dp).semantics {
+ role = Role.Button
+ contentDescription = label
+ },
+ color = MaterialTheme.colorScheme.primaryContainer,
+ shape = RoundedCornerShape(28.dp),
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ onClick = onClick,
+ ) {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ }
}
}
Text(
- modifier = Modifier.clearAndSetSemantics {},
+ modifier = Modifier.clearAndSetSemantics {}.basicMarquee(),
text = label,
style = MaterialTheme.typography.labelMedium,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
+ maxLines = 2,
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index f6f07a5..780e3f2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -16,28 +16,28 @@
package com.android.systemui.volume.panel.component.button.ui.composable
-import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedIconToggleButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
@@ -62,32 +62,38 @@
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
- OutlinedIconToggleButton(
- modifier =
- Modifier.height(64.dp).fillMaxWidth().semantics {
- role = Role.Switch
- contentDescription = label
- },
- checked = viewModel.isChecked,
- onCheckedChange = onCheckedChange,
- shape = RoundedCornerShape(28.dp),
- colors =
- IconButtonDefaults.outlinedIconToggleButtonColors(
- containerColor = MaterialTheme.colorScheme.surface,
- contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
- checkedContainerColor = MaterialTheme.colorScheme.primaryContainer,
- checkedContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
- ),
- border = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
- ) {
- Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ BottomComponentButtonSurface {
+ val colors =
+ if (viewModel.isChecked) {
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ )
+ } else {
+ ButtonDefaults.buttonColors(
+ containerColor = Color.Transparent,
+ contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
+ Button(
+ modifier =
+ Modifier.fillMaxSize().padding(8.dp).semantics {
+ role = Role.Switch
+ contentDescription = label
+ },
+ onClick = { onCheckedChange(!viewModel.isChecked) },
+ shape = RoundedCornerShape(28.dp),
+ colors = colors
+ ) {
+ Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ }
}
+
Text(
- modifier = Modifier.clearAndSetSemantics {},
+ modifier = Modifier.clearAndSetSemantics {}.basicMarquee(),
text = label,
style = MaterialTheme.typography.labelMedium,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
+ maxLines = 2,
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
index 26086d1..9f9bc62 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -33,6 +33,8 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.paneTitle
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
@@ -82,7 +84,8 @@
title: @Composable (SystemUIDialog) -> Unit,
content: @Composable (SystemUIDialog) -> Unit,
) {
- Box(Modifier.fillMaxWidth()) {
+ val paneTitle = stringResource(R.string.accessibility_volume_settings)
+ Box(Modifier.fillMaxWidth().semantics(properties = { this.paneTitle = paneTitle })) {
Column(
modifier = Modifier.fillMaxWidth().padding(vertical = 20.dp),
verticalArrangement = Arrangement.spacedBy(20.dp),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
index b0b5a7d..c743314 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -21,6 +21,7 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
@@ -45,6 +46,12 @@
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.selected
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
@@ -111,10 +118,16 @@
horizontalArrangement = Arrangement.spacedBy(spacing)
) {
for (itemIndex in items.indices) {
+ val item = items[itemIndex]
Row(
modifier =
Modifier.height(48.dp)
.weight(1f)
+ .semantics {
+ item.contentDescription?.let { contentDescription = it }
+ role = Role.Switch
+ selected = itemIndex == scope.selectedIndex
+ }
.clickable(
interactionSource = null,
indication = null,
@@ -123,7 +136,6 @@
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
- val item = items[itemIndex]
if (item.icon !== Empty) {
with(items[itemIndex]) { icon() }
}
@@ -137,13 +149,17 @@
start = indicatorBackgroundPadding,
top = labelIndicatorBackgroundSpacing,
end = indicatorBackgroundPadding
- ),
+ )
+ .clearAndSetSemantics {},
horizontalArrangement = Arrangement.spacedBy(spacing),
) {
for (itemIndex in items.indices) {
+ val cornersRadius = 4.dp
TextButton(
modifier = Modifier.weight(1f),
onClick = { items[itemIndex].onItemSelected() },
+ shape = RoundedCornerShape(cornersRadius),
+ contentPadding = PaddingValues(cornersRadius)
) {
val item = items[itemIndex]
if (item.icon !== Empty) {
@@ -292,6 +308,7 @@
onItemSelected: () -> Unit,
icon: @Composable RowScope.() -> Unit = Empty,
label: @Composable RowScope.() -> Unit = Empty,
+ contentDescription: String? = null,
)
}
@@ -313,6 +330,7 @@
onItemSelected: () -> Unit,
icon: @Composable RowScope.() -> Unit,
label: @Composable RowScope.() -> Unit,
+ contentDescription: String?,
) {
require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" }
if (isSelected) {
@@ -323,6 +341,7 @@
onItemSelected = onItemSelected,
icon = icon,
label = label,
+ contentDescription = contentDescription,
)
)
}
@@ -336,6 +355,7 @@
val onItemSelected: () -> Unit,
val icon: @Composable RowScope.() -> Unit,
val label: @Composable RowScope.() -> Unit,
+ val contentDescription: String?,
)
private const val UNSET_OFFSET = -1
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 71b3e8a..eed54da 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -16,12 +16,14 @@
package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
+import androidx.compose.foundation.basicMarquee
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import com.android.systemui.animation.Expandable
@@ -49,6 +51,7 @@
@Composable
private fun Title() {
Text(
+ modifier = Modifier.basicMarquee(),
text = stringResource(R.string.volume_panel_spatial_audio_title),
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center,
@@ -71,9 +74,11 @@
}
VolumePanelRadioButtonBar {
for (buttonViewModel in enabledModelStates) {
+ val label = buttonViewModel.button.label.toString()
item(
isSelected = buttonViewModel.button.isChecked,
onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
+ contentDescription = label,
icon = {
Icon(
icon = buttonViewModel.button.icon,
@@ -82,9 +87,12 @@
},
label = {
Text(
- text = buttonViewModel.button.label.toString(),
+ modifier = Modifier.basicMarquee(),
+ text = label,
style = MaterialTheme.typography.labelMedium,
color = buttonViewModel.labelColor.toColor(),
+ textAlign = TextAlign.Center,
+ maxLines = 2
)
}
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index d31064a..19d3f59 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -17,10 +17,10 @@
package com.android.systemui.volume.panel.component.volume.ui.composable
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -32,7 +32,6 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
@@ -130,24 +129,20 @@
isTappable: Boolean,
modifier: Modifier = Modifier
) {
- if (isTappable) {
- IconButton(
- modifier = modifier,
- onClick = onIconTapped,
- colors =
- IconButtonColors(
- contentColor = LocalContentColor.current,
- containerColor = Color.Transparent,
- disabledContentColor = LocalContentColor.current,
- disabledContainerColor = Color.Transparent,
- ),
- content = { Icon(modifier = Modifier.size(24.dp), icon = icon) },
- )
- } else {
- Box(
- modifier = modifier,
- contentAlignment = Alignment.Center,
- content = { Icon(modifier = Modifier.size(24.dp), icon = icon) },
- )
- }
+ val boxModifier =
+ if (isTappable) {
+ modifier.clickable(
+ onClick = onIconTapped,
+ interactionSource = null,
+ indication = null
+ )
+ } else {
+ modifier
+ }
+ .fillMaxSize()
+ Box(
+ modifier = boxModifier,
+ contentAlignment = Alignment.Center,
+ content = { Icon(modifier = Modifier.size(24.dp), icon = icon) },
+ )
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index dd76781..9ea20b9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -20,6 +20,7 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
@@ -27,6 +28,7 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.fastSumBy
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
@Composable
@@ -53,6 +55,14 @@
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
horizontalArrangement = Arrangement.spacedBy(if (isLargeScreen) 28.dp else 20.dp),
) {
+ val visibleComponentsCount =
+ layout.footerComponents.fastSumBy { if (it.isVisible) 1 else 0 }
+
+ // Center footer component if there is only one present
+ if (visibleComponentsCount == 1) {
+ Spacer(modifier = Modifier.weight(0.5f))
+ }
+
for (component in layout.footerComponents) {
AnimatedVisibility(
visible = component.isVisible,
@@ -63,6 +73,10 @@
}
}
}
+
+ if (visibleComponentsCount == 1) {
+ Spacer(modifier = Modifier.weight(0.5f))
+ }
}
}
}
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 e7cb5a4..a8a1d88 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
@@ -261,16 +261,19 @@
scene: Scene,
element: Element,
): Boolean {
- val transition = layoutImpl.state.currentTransition
+ val transition = layoutImpl.state.currentTransition ?: return true
- // Always draw the element if there is no ongoing transition or if the element is not shared or
- // if the current scene is the one that is currently over scrolling with [OverscrollSpec].
- if (
- transition == null ||
- transition.fromScene !in element.sceneStates ||
- transition.toScene !in element.sceneStates ||
- transition.currentOverscrollSpec?.scene == scene.key
- ) {
+ val inFromScene = transition.fromScene in element.sceneStates
+ val inToScene = transition.toScene in element.sceneStates
+
+ // If an element is not present in any scene, it should not be drawn.
+ if (!inFromScene && !inToScene) {
+ return false
+ }
+
+ // Always draw if the element is not shared or if the current scene is the one that is currently
+ // over scrolling with [OverscrollSpec].
+ if (!inFromScene || !inToScene || transition.currentOverscrollSpec?.scene == scene.key) {
return true
}
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 2453e25..458a2b9 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
@@ -45,6 +45,7 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.intermediateLayout
import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
@@ -175,6 +176,60 @@
}
@Test
+ fun elementsNotInTransition_shouldNotBeDrawn() {
+ val nFrames = 20
+ val frameDuration = 16L
+ val tween = tween<Float>(nFrames * frameDuration.toInt())
+ val layoutSize = 100.dp
+ val elementSize = 50.dp
+ val elementOffset = 20.dp
+
+ lateinit var changeScene: (SceneKey) -> Unit
+
+ rule.testTransition(
+ from = TestScenes.SceneA,
+ to = TestScenes.SceneB,
+ transitionLayout = { currentScene, onChangeScene ->
+ changeScene = onChangeScene
+
+ SceneTransitionLayout(
+ currentScene,
+ onChangeScene,
+ transitions {
+ from(TestScenes.SceneA, to = TestScenes.SceneB) { spec = tween }
+ from(TestScenes.SceneB, to = TestScenes.SceneC) { spec = tween }
+ },
+ ) {
+ scene(TestScenes.SceneA) {
+ Box(Modifier.size(layoutSize)) {
+ // Transformed element
+ Element(
+ TestElements.Bar,
+ elementSize,
+ elementOffset,
+ )
+ }
+ }
+ scene(TestScenes.SceneB) { Box(Modifier.size(layoutSize)) }
+ scene(TestScenes.SceneC) { Box(Modifier.size(layoutSize)) }
+ }
+ },
+ ) {
+ // Start transition from SceneA to SceneB
+ at(1 * frameDuration) {
+ onElement(TestElements.Bar).assertExists()
+
+ // Start transition from SceneB to SceneC
+ changeScene(TestScenes.SceneC)
+ }
+
+ at(2 * frameDuration) { onElement(TestElements.Bar).assertIsNotDisplayed() }
+
+ at(3 * frameDuration) { onElement(TestElements.Bar).assertDoesNotExist() }
+ }
+ }
+
+ @Test
fun onlyMovingElements_noLayout_onlyPlacement() {
val nFrames = 20
val layoutSize = 100.dp
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index cb8cebf..81878aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -179,10 +179,14 @@
setAutoConfirmFeatureEnabled(true)
}
assertThat(isAutoConfirmEnabled).isTrue()
- val shorterPin =
- FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply { removeLast() }
+ val defaultPin = FakeAuthenticationRepository.DEFAULT_PIN.toMutableList()
- assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true))
+ assertSkipped(
+ underTest.authenticate(
+ defaultPin.subList(0, defaultPin.size - 1),
+ tryAutoConfirm = true
+ )
+ )
assertThat(underTest.lockoutEndTimestamp).isNull()
assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
}
@@ -201,7 +205,6 @@
assertFailed(
underTest.authenticate(wrongPin, tryAutoConfirm = true),
- assertNoResultEvents = true,
)
}
@@ -219,7 +222,6 @@
assertFailed(
underTest.authenticate(longerPin, tryAutoConfirm = true),
- assertNoResultEvents = true,
)
}
@@ -547,14 +549,9 @@
private fun assertFailed(
authenticationResult: AuthenticationResult,
- assertNoResultEvents: Boolean = false,
) {
assertThat(authenticationResult).isEqualTo(AuthenticationResult.FAILED)
- if (assertNoResultEvents) {
- assertThat(onAuthenticationResult).isNull()
- } else {
- assertThat(onAuthenticationResult).isFalse()
- }
+ assertThat(onAuthenticationResult).isFalse()
}
private fun assertSkipped(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index a0db482..5bb36a0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -252,7 +252,14 @@
@Test
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
+ // TODO(b/332768183) remove this after the bug if fixed.
+ // Collect the flow so that it is hot, in the real application this is done by using a
+ // refreshingFlow that relies on the UI to make this flow hot.
+ val autoConfirmEnabled by
+ collectLastValue(authenticationInteractor.isAutoConfirmEnabled)
kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
+
+ assertThat(autoConfirmEnabled).isTrue()
val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
lockDeviceAndOpenPinBouncer()
@@ -264,9 +271,17 @@
@Test
fun onAutoConfirm_whenWrong() =
testScope.runTest {
+ // TODO(b/332768183) remove this after the bug if fixed.
+ // Collect the flow so that it is hot, in the real application this is done by using a
+ // refreshingFlow that relies on the UI to make this flow hot.
+ val autoConfirmEnabled by
+ collectLastValue(authenticationInteractor.isAutoConfirmEnabled)
+
val currentScene by collectLastValue(sceneInteractor.currentScene)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
+
+ assertThat(autoConfirmEnabled).isTrue()
lockDeviceAndOpenPinBouncer()
FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 36919d0..9a13bb2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -827,7 +827,7 @@
}
@Test
- fun isAuthenticatedIsResetToFalseWhenKeyguardDoneAnimationsFinished() =
+ fun isAuthenticatedIsResetToFalseWhenTransitioningToGone() =
testScope.runTest {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
@@ -840,7 +840,13 @@
assertThat(authenticated()).isTrue()
- keyguardRepository.keyguardDoneAnimationsFinished()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
assertThat(authenticated()).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index 27c4ec1..f2eb7f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -35,6 +35,7 @@
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSComponent
import com.android.systemui.qs.dagger.QSSceneComponent
+import com.android.systemui.settings.brightness.MirrorController
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
@@ -496,4 +497,33 @@
runCurrent()
verify(qsImpl!!).setInSplitShade(true)
}
+
+ @Test
+ fun requestCloseCustomizer() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ underTest.requestCloseCustomizer()
+ verify(qsImpl!!).closeCustomizer()
+ }
+
+ @Test
+ fun setBrightnessMirrorController() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+
+ underTest.inflate(context)
+ runCurrent()
+
+ val mirrorController = mock<MirrorController>()
+ underTest.setBrightnessMirrorController(mirrorController)
+
+ verify(qsImpl!!).setBrightnessMirrorController(mirrorController)
+
+ underTest.setBrightnessMirrorController(null)
+ verify(qsImpl!!).setBrightnessMirrorController(null)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index ef38567..426f94d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -108,6 +109,7 @@
underTest =
QuickSettingsSceneViewModel(
+ brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
@@ -133,18 +135,13 @@
}
@Test
- fun destinationsCustomizing() =
+ fun destinationsCustomizing_noDestinations() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, false)
val destinations by collectLastValue(underTest.destinationScenes)
qsFlexiglassAdapter.setCustomizing(true)
- assertThat(destinations)
- .isEqualTo(
- mapOf(
- Back to UserActionResult(Scenes.QuickSettings),
- )
- )
+ assertThat(destinations).isEmpty()
}
@Test
@@ -164,18 +161,13 @@
}
@Test
- fun destinations_whenCustomizing_inSplitShade() =
+ fun destinations_whenCustomizing_inSplitShade_noDestinations() =
testScope.runTest {
overrideResource(R.bool.config_use_split_notification_shade, true)
val destinations by collectLastValue(underTest.destinationScenes)
qsFlexiglassAdapter.setCustomizing(true)
- assertThat(destinations)
- .isEqualTo(
- mapOf(
- Back to UserActionResult(Scenes.QuickSettings),
- )
- )
+ assertThat(destinations).isEmpty()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 98cbda2..9856f90 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -74,6 +74,7 @@
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -259,6 +260,7 @@
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
+ brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
mediaDataManager = mediaDataManager,
shadeInteractor = kosmos.shadeInteractor,
footerActionsController = kosmos.footerActionsController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 3d66192..ae31058 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -140,4 +140,23 @@
ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
+
+ @Test
+ fun previousScene() =
+ testScope.runTest {
+ val underTest = kosmos.sceneContainerRepository
+ val currentScene by collectLastValue(underTest.currentScene)
+ val previousScene by collectLastValue(underTest.previousScene)
+
+ assertThat(previousScene).isNull()
+
+ val firstScene = currentScene
+ underTest.changeScene(Scenes.Shade)
+ assertThat(previousScene).isEqualTo(firstScene)
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+
+ underTest.changeScene(Scenes.QuickSettings)
+ assertThat(previousScene).isEqualTo(Scenes.Shade)
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 143c0f2..b179c30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -291,4 +291,19 @@
assertThat(isVisible).isFalse()
}
+
+ @Test
+ fun previousScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ val previousScene by collectLastValue(underTest.previousScene)
+ assertThat(previousScene).isNull()
+
+ val firstScene = currentScene
+ underTest.changeScene(toScene = Scenes.Shade, "reason")
+ assertThat(previousScene).isEqualTo(firstScene)
+
+ underTest.changeScene(toScene = Scenes.QuickSettings, "reason")
+ assertThat(previousScene).isEqualTo(Scenes.Shade)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt
new file mode 100644
index 0000000..a1af70b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorShowingRepositoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val underTest = BrightnessMirrorShowingRepository()
+
+ @Test
+ fun isShowing_setAndFlow() =
+ kosmos.testScope.runTest {
+ val isShowing by collectLastValue(underTest.isShowing)
+
+ assertThat(isShowing).isFalse()
+
+ underTest.setMirrorShowing(true)
+ assertThat(isShowing).isTrue()
+
+ underTest.setMirrorShowing(false)
+ assertThat(isShowing).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt
new file mode 100644
index 0000000..31d6df2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorShowingInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val underTest =
+ BrightnessMirrorShowingInteractor(kosmos.brightnessMirrorShowingRepository)
+
+ @Test
+ fun isShowing_setAndFlow() =
+ kosmos.testScope.runTest {
+ val isShowing by collectLastValue(underTest.isShowing)
+
+ assertThat(isShowing).isFalse()
+
+ underTest.setMirrorShowing(true)
+ assertThat(isShowing).isTrue()
+
+ underTest.setMirrorShowing(false)
+ assertThat(isShowing).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt
new file mode 100644
index 0000000..6de7f40
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.binder
+
+import android.content.applicationContext
+import android.view.ContextThemeWrapper
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightnessSliderControllerFactory
+import com.android.systemui.testKosmos
+import com.android.systemui.util.Assert
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorInflaterTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val themedContext =
+ ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings)
+
+ @Test
+ fun inflate_sliderViewAddedToFrame() {
+ Assert.setTestThread(Thread.currentThread())
+
+ val (frame, sliderController) =
+ BrightnessMirrorInflater.inflate(
+ themedContext,
+ kosmos.brightnessSliderControllerFactory
+ )
+
+ assertThat(sliderController.rootView.parent).isSameInstanceAs(frame)
+
+ Assert.setTestThread(null)
+ }
+
+ @Test
+ fun inflate_frameAndSliderViewVisible() {
+ Assert.setTestThread(Thread.currentThread())
+
+ val (frame, sliderController) =
+ BrightnessMirrorInflater.inflate(
+ themedContext,
+ kosmos.brightnessSliderControllerFactory,
+ )
+
+ assertThat(frame.visibility).isEqualTo(View.VISIBLE)
+ assertThat(sliderController.rootView.visibility).isEqualTo(View.VISIBLE)
+
+ Assert.setTestThread(null)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt
new file mode 100644
index 0000000..09fc6f9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.viewmodel
+
+import android.content.applicationContext
+import android.content.res.mainResources
+import android.view.ContextThemeWrapper
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.settings.brightness.ui.viewModel.LocationAndSize
+import com.android.systemui.settings.brightnessSliderControllerFactory
+import com.android.systemui.testKosmos
+import com.android.systemui.util.Assert
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessMirrorViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val themedContext =
+ ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings)
+
+ private val underTest =
+ with(kosmos) {
+ BrightnessMirrorViewModel(
+ brightnessMirrorShowingInteractor,
+ mainResources,
+ brightnessSliderControllerFactory,
+ )
+ }
+
+ @Test
+ fun showHideMirror_isShowing() =
+ with(kosmos) {
+ testScope.runTest {
+ val showing by collectLastValue(underTest.isShowing)
+
+ assertThat(showing).isFalse()
+
+ underTest.showMirror()
+ assertThat(showing).isTrue()
+
+ underTest.hideMirror()
+ assertThat(showing).isFalse()
+ }
+ }
+
+ @Test
+ fun setLocationInWindow_correctLocationAndSize() =
+ with(kosmos) {
+ testScope.runTest {
+ val locationAndSize by collectLastValue(underTest.locationAndSize)
+
+ val x = 20
+ val y = 100
+ val height = 50
+ val width = 200
+ val padding =
+ mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+
+ val mockView =
+ mock<View> {
+ whenever(getLocationInWindow(any())).then {
+ it.getArgument<IntArray>(0).apply {
+ this[0] = x
+ this[1] = y
+ }
+ Unit
+ }
+
+ whenever(measuredHeight).thenReturn(height)
+ whenever(measuredWidth).thenReturn(width)
+ }
+
+ underTest.setLocationAndSize(mockView)
+
+ assertThat(locationAndSize)
+ .isEqualTo(
+ // Adjust for padding around the view
+ LocationAndSize(
+ yOffset = y - padding,
+ width = width + 2 * padding,
+ height = height + 2 * padding,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun setLocationInWindow_paddingSetToRootView() =
+ with(kosmos) {
+ Assert.setTestThread(Thread.currentThread())
+ val padding =
+ mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+
+ val view = mock<View>()
+
+ val (_, sliderController) =
+ BrightnessMirrorInflater.inflate(
+ themedContext,
+ brightnessSliderControllerFactory,
+ )
+ underTest.setToggleSlider(sliderController)
+
+ underTest.setLocationAndSize(view)
+
+ with(sliderController.rootView) {
+ assertThat(paddingBottom).isEqualTo(padding)
+ assertThat(paddingTop).isEqualTo(padding)
+ assertThat(paddingLeft).isEqualTo(padding)
+ assertThat(paddingRight).isEqualTo(padding)
+ }
+
+ Assert.setTestThread(null)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 77109d6..7a681b3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
@@ -127,6 +128,7 @@
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsSceneAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
+ brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
mediaDataManager = mediaDataManager,
shadeInteractor = kosmos.shadeInteractor,
footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 3c28c0e..a3cf929 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -32,7 +32,6 @@
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
-import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationScrollViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
@@ -67,8 +66,8 @@
fun updateBounds() =
testScope.runTest {
val radius = MutableStateFlow(32)
- val viewPosition = MutableStateFlow(ViewPosition(0, 0))
- val shape by collectLastValue(appearanceViewModel.shadeScrimShape(radius, viewPosition))
+ val leftOffset = MutableStateFlow(0)
+ val shape by collectLastValue(appearanceViewModel.shadeScrimShape(radius, leftOffset))
placeholderViewModel.onScrimBoundsChanged(
ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f)
@@ -83,7 +82,7 @@
)
)
- viewPosition.value = ViewPosition(200, 15)
+ leftOffset.value = 200
radius.value = 24
placeholderViewModel.onScrimBoundsChanged(
ShadeScrimBounds(left = 210f, top = 200f, right = 300f, bottom = 550f)
@@ -92,7 +91,7 @@
.isEqualTo(
ShadeScrimShape(
bounds =
- ShadeScrimBounds(left = 10f, top = 185f, right = 100f, bottom = 535f),
+ ShadeScrimBounds(left = 10f, top = 200f, right = 100f, bottom = 550f),
topRadius = 24,
bottomRadius = 0
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 509a82d..ac8387f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -19,14 +19,17 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -136,12 +139,12 @@
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
- val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ val paddingTop by collectLastValue(underTest.paddingTopDimen)
configurationRepository.onAnyConfigurationChange()
// Should directly use the header height (flagged off value)
- assertThat(dimens!!.paddingTop).isEqualTo(10)
+ assertThat(paddingTop).isEqualTo(10)
}
@Test
@@ -154,11 +157,11 @@
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
- val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ val paddingTop by collectLastValue(underTest.paddingTopDimen)
configurationRepository.onAnyConfigurationChange()
// Should directly use the header height (flagged on value)
- assertThat(dimens!!.paddingTop).isEqualTo(5)
+ assertThat(paddingTop).isEqualTo(5)
}
@Test
@@ -168,11 +171,11 @@
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
- val dimens by collectLastValue(underTest.configurationBasedDimensions)
+ val paddingTop by collectLastValue(underTest.paddingTopDimen)
configurationRepository.onAnyConfigurationChange()
- assertThat(dimens!!.paddingTop).isEqualTo(0)
+ assertThat(paddingTop).isEqualTo(0)
}
@Test
@@ -200,9 +203,9 @@
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX, FLAG_SCENE_CONTAINER)
fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_usesResource() =
testScope.runTest {
- mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
val headerResourceHeight = 50
val headerHelperHeight = 100
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
@@ -219,6 +222,27 @@
}
@Test
+ @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
+ @EnableSceneContainer
+ fun validateMarginTopWithLargeScreenHeader_refactorFlagOff_sceneContainerFlagOn_stillZero() =
+ testScope.runTest {
+ val headerResourceHeight = 50
+ val headerHelperHeight = 100
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(headerHelperHeight)
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+
+ assertThat(dimens!!.marginTop).isEqualTo(0)
+ }
+
+ @Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun validateMarginTopWithLargeScreenHeader_refactorFlagOn_usesHelper() =
testScope.runTest {
mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
@@ -238,6 +262,26 @@
}
@Test
+ @EnableSceneContainer
+ fun validateMarginTopWithLargeScreenHeader_sceneContainerFlagOn_stillZero() =
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
+ val headerResourceHeight = 50
+ val headerHelperHeight = 100
+ whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+ .thenReturn(headerHelperHeight)
+ overrideResource(R.bool.config_use_large_screen_shade_header, true)
+ overrideResource(R.dimen.large_screen_shade_header_height, headerResourceHeight)
+ overrideResource(R.dimen.notification_panel_margin_top, 0)
+
+ val dimens by collectLastValue(underTest.configurationBasedDimensions)
+
+ configurationRepository.onAnyConfigurationChange()
+
+ assertThat(dimens!!.marginTop).isEqualTo(0)
+ }
+
+ @Test
fun glanceableHubAlpha_lockscreenToHub() =
testScope.runTest {
val alpha by collectLastValue(underTest.glanceableHubAlpha)
@@ -661,6 +705,7 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun translationYUpdatesOnKeyguard() =
testScope.runTest {
val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
index 173d57b..3b6b5a0 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -91,6 +91,7 @@
android:layout_height="wrap_content"
android:contentDescription="@string/keyguard_accessibility_password"
android:gravity="center_horizontal"
+ android:layout_gravity="center"
android:imeOptions="flagForceAscii|actionDone"
android:inputType="textPassword"
android:maxLength="500"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 909d4fc..5aac653 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -55,6 +55,7 @@
android:layout_height="wrap_content"
android:contentDescription="@string/keyguard_accessibility_password"
android:gravity="center"
+ android:layout_gravity="center"
android:singleLine="true"
android:textStyle="normal"
android:inputType="textPassword"
diff --git a/packages/SystemUI/res/drawable/ic_bugreport.xml b/packages/SystemUI/res/drawable/ic_bugreport.xml
new file mode 100644
index 0000000..ed1c6c7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_bugreport.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,14h4v2h-4z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M10,10h4v2h-4z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
index 1777bdf..dabfe9d 100644
--- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml
@@ -39,7 +39,6 @@
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.8"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
@@ -60,11 +59,12 @@
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
- android:fillViewport="true"
- android:padding="24dp"
- app:layout_constrainedHeight="true"
- app:layout_constrainedWidth="true"
- app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+ android:paddingBottom="16dp"
+ android:paddingLeft="24dp"
+ android:paddingRight="12dp"
+ android:paddingTop="24dp"
+ android:fadeScrollbars="false"
+ app:layout_constraintBottom_toTopOf="@+id/button_bar"
app:layout_constraintEnd_toStartOf="@+id/midGuideline"
app:layout_constraintStart_toStartOf="@id/leftGuideline"
app:layout_constraintTop_toTopOf="@+id/topGuideline">
@@ -91,7 +91,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
- android:paddingLeft="8dp"
+ android:paddingLeft="16dp"
app:layout_constraintBottom_toBottomOf="@+id/logo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/logo"
@@ -119,7 +119,7 @@
style="@style/TextAppearance.AuthCredential.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="12dp"
+ android:layout_marginTop="16dp"
android:gravity="@integer/biometric_dialog_text_gravity"
android:paddingHorizontal="0dp"
android:textAlignment="viewStart"
@@ -133,6 +133,7 @@
android:id="@+id/customized_view_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginTop="24dp"
android:gravity="center_vertical"
android:orientation="vertical"
android:visibility="gone"
@@ -148,6 +149,7 @@
style="@style/TextAppearance.AuthCredential.Description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="24dp"
android:gravity="@integer/biometric_dialog_text_gravity"
android:paddingHorizontal="0dp"
android:textAlignment="viewStart"
@@ -179,7 +181,7 @@
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
- app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+ app:layout_constraintBottom_toTopOf="@+id/button_bar"
app:layout_constraintEnd_toEndOf="@+id/biometric_icon"
app:layout_constraintStart_toStartOf="@+id/biometric_icon"
app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
@@ -189,7 +191,7 @@
android:id="@+id/button_bar"
layout="@layout/biometric_prompt_button_bar"
android:layout_width="0dp"
- android:layout_height="0dp"
+ android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
app:layout_constraintEnd_toEndOf="@id/scrollView"
app:layout_constraintStart_toStartOf="@id/scrollView"
@@ -204,14 +206,6 @@
app:barrierDirection="top"
app:constraint_referenced_ids="scrollView" />
- <androidx.constraintlayout.widget.Barrier
- android:id="@+id/buttonBarrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:barrierAllowsGoneWidgets="false"
- app:barrierDirection="top"
- app:constraint_referenced_ids="button_bar" />
-
<androidx.constraintlayout.widget.Guideline
android:id="@+id/leftGuideline"
android:layout_width="wrap_content"
@@ -238,13 +232,13 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
- app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+ app:layout_constraintGuide_end="40dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topGuideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
- app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" />
+ app:layout_constraintGuide_begin="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
index 8b886a7..240abab 100644
--- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml
@@ -29,28 +29,31 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/rightGuideline"
app:layout_constraintStart_toStartOf="@+id/leftGuideline"
- app:layout_constraintTop_toTopOf="@+id/topBarrier" />
+ app:layout_constraintTop_toTopOf="@+id/topBarrier"
+ app:layout_constraintWidth_max="640dp" />
<include
- layout="@layout/biometric_prompt_button_bar"
android:id="@+id/button_bar"
+ layout="@layout/biometric_prompt_button_bar"
android:layout_width="0dp"
- android:layout_height="match_parent"
- app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="40dp"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/panel"
- app:layout_constraintStart_toStartOf="@id/panel"/>
+ app:layout_constraintStart_toStartOf="@id/panel" />
<ScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="wrap_content"
+ android:fadeScrollbars="false"
android:fillViewport="true"
- android:paddingBottom="36dp"
- android:paddingHorizontal="24dp"
+ android:paddingBottom="32dp"
+ android:paddingHorizontal="32dp"
android:paddingTop="24dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
- app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
+ app:layout_constraintBottom_toTopOf="@+id/scrollBarrier"
app:layout_constraintEnd_toEndOf="@id/panel"
app:layout_constraintStart_toStartOf="@id/panel"
app:layout_constraintTop_toTopOf="@+id/topGuideline"
@@ -63,10 +66,10 @@
<ImageView
android:id="@+id/logo"
- android:contentDescription="@string/biometric_dialog_logo"
android:layout_width="@dimen/biometric_prompt_logo_size"
android:layout_height="@dimen/biometric_prompt_logo_size"
android:layout_gravity="center"
+ android:contentDescription="@string/biometric_dialog_logo"
android:scaleType="fitXY"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/logo_description"
@@ -79,6 +82,7 @@
style="@style/TextAppearance.AuthCredential.LogoDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingTop="16dp"
app:layout_constraintBottom_toTopOf="@+id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -114,6 +118,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
+ android:paddingTop="24dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -126,6 +131,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="@integer/biometric_dialog_text_gravity"
+ android:paddingTop="16dp"
+ android:textAlignment="viewStart"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -153,7 +160,7 @@
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
- app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+ app:layout_constraintBottom_toTopOf="@+id/button_bar"
app:layout_constraintEnd_toEndOf="@+id/panel"
app:layout_constraintStart_toStartOf="@+id/panel"
app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
@@ -172,12 +179,12 @@
<!-- Try Again Button -->
<androidx.constraintlayout.widget.Barrier
- android:id="@+id/buttonBarrier"
+ android:id="@+id/scrollBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierAllowsGoneWidgets="false"
app:barrierDirection="top"
- app:constraint_referenced_ids="button_bar" />
+ app:constraint_referenced_ids="biometric_icon, button_bar" />
<!-- Guidelines for setting panel border -->
<androidx.constraintlayout.widget.Guideline
@@ -199,14 +206,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
- app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+ app:layout_constraintGuide_end="40dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topGuideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
- app:layout_constraintGuide_percent="0.25171" />
+ app:layout_constraintGuide_begin="56dp" />
<com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
android:id="@+id/biometric_icon"
@@ -216,7 +223,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.8"
+ app:layout_constraintVertical_bias="1.0"
tools:srcCompat="@tools:sample/avatars" />
<com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
index 810c7433..4d2310a 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml
@@ -17,12 +17,13 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:theme="@style/Theme.SystemUI.Dialog"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Negative Button, reserved for app -->
<Button
android:id="@+id/button_negative"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@@ -36,7 +37,7 @@
<!-- Cancel Button, replaces negative button when biometric is accepted -->
<Button
android:id="@+id/button_cancel"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@@ -49,7 +50,7 @@
<!-- "Use Credential" Button, replaces if device credential is allowed -->
<Button
android:id="@+id/button_use_credential"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@@ -61,7 +62,7 @@
<!-- Positive Button -->
<Button
android:id="@+id/button_confirm"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ style="@style/Widget.Dialog.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
@@ -76,7 +77,7 @@
<!-- Try Again Button -->
<Button
android:id="@+id/button_try_again"
- style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ style="@style/Widget.Dialog.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index 74bf318..8e3cf4d 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -35,24 +35,26 @@
android:id="@+id/button_bar"
layout="@layout/biometric_prompt_button_bar"
android:layout_width="0dp"
- android:layout_height="match_parent"
- app:layout_constraintBottom_toTopOf="@id/bottomGuideline"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="40dp"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/panel"
app:layout_constraintStart_toStartOf="@id/panel" />
<ScrollView
android:id="@+id/scrollView"
- android:layout_width="match_parent"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
android:fillViewport="true"
+ android:fadeScrollbars="false"
android:paddingBottom="36dp"
android:paddingHorizontal="24dp"
android:paddingTop="24dp"
app:layout_constrainedHeight="true"
app:layout_constrainedWidth="true"
- app:layout_constraintBottom_toTopOf="@+id/biometric_icon"
- app:layout_constraintEnd_toEndOf="@id/rightGuideline"
- app:layout_constraintStart_toStartOf="@id/leftGuideline"
+ app:layout_constraintBottom_toTopOf="@+id/scrollBarrier"
+ app:layout_constraintEnd_toEndOf="@id/panel"
+ app:layout_constraintStart_toStartOf="@id/panel"
app:layout_constraintTop_toTopOf="@+id/topGuideline"
app:layout_constraintVertical_bias="1.0">
@@ -68,6 +70,7 @@
android:layout_height="@dimen/biometric_prompt_logo_size"
android:layout_gravity="center"
android:scaleType="fitXY"
+ android:importantForAccessibility="no"
app:layout_constraintBottom_toTopOf="@+id/logo_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -79,6 +82,7 @@
style="@style/TextAppearance.AuthCredential.LogoDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingTop="8dp"
app:layout_constraintBottom_toTopOf="@+id/title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -114,8 +118,8 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
- android:visibility="gone"
android:paddingTop="24dp"
+ android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -127,7 +131,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="@integer/biometric_dialog_text_gravity"
- android:paddingTop="24dp"
+ android:textAlignment="viewStart"
+ android:paddingTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -154,7 +159,7 @@
android:fadingEdge="horizontal"
android:gravity="center_horizontal"
android:scrollHorizontally="true"
- app:layout_constraintBottom_toTopOf="@+id/buttonBarrier"
+ app:layout_constraintBottom_toTopOf="@+id/button_bar"
app:layout_constraintEnd_toEndOf="@+id/panel"
app:layout_constraintStart_toStartOf="@+id/panel"
app:layout_constraintTop_toBottomOf="@+id/biometric_icon"
@@ -169,12 +174,12 @@
app:constraint_referenced_ids="scrollView" />
<androidx.constraintlayout.widget.Barrier
- android:id="@+id/buttonBarrier"
+ android:id="@+id/scrollBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierAllowsGoneWidgets="false"
app:barrierDirection="top"
- app:constraint_referenced_ids="button_bar" />
+ app:constraint_referenced_ids="biometric_icon, button_bar" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/leftGuideline"
@@ -195,14 +200,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
- app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" />
+ app:layout_constraintGuide_end="40dp" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/topGuideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
- app:layout_constraintGuide_percent="0.25" />
+ app:layout_constraintGuide_begin="119dp" />
<com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
android:id="@+id/biometric_icon"
@@ -212,7 +217,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintVertical_bias="0.8"
+ app:layout_constraintVertical_bias="1.0"
tools:srcCompat="@tools:sample/avatars" />
<com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
diff --git a/packages/SystemUI/res/layout/record_issue_dialog.xml b/packages/SystemUI/res/layout/record_issue_dialog.xml
index 53ad9f1..30d7b0a 100644
--- a/packages/SystemUI/res/layout/record_issue_dialog.xml
+++ b/packages/SystemUI/res/layout/record_issue_dialog.xml
@@ -54,6 +54,7 @@
android:layout_weight="0"
android:src="@drawable/ic_screenrecord"
app:tint="?androidprv:attr/materialColorOnSurface"
+ android:importantForAccessibility="no"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/screenrecord_option_padding" />
@@ -78,4 +79,44 @@
android:layout_weight="0"
android:contentDescription="@string/quick_settings_screen_record_label" />
</LinearLayout>
+
+ <!-- Bug Report Switch -->
+ <LinearLayout
+ android:id="@+id/bugreport_switch_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/qqs_layout_margin_top"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="@dimen/screenrecord_option_icon_size"
+ android:layout_height="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="0"
+ android:src="@drawable/ic_bugreport"
+ app:tint="?androidprv:attr/materialColorOnSurface"
+ android:importantForAccessibility="no"
+ android:layout_gravity="center"
+ android:layout_marginEnd="@dimen/screenrecord_option_padding" />
+
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="center"
+ android:text="@string/qs_record_issue_bug_report"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:importantForAccessibility="no"/>
+
+ <Switch
+ android:id="@+id/bugreport_switch"
+ android:layout_width="wrap_content"
+ android:minHeight="@dimen/screenrecord_option_icon_size"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_gravity="fill_vertical"
+ android:layout_weight="0"
+ android:contentDescription="@string/qs_record_issue_bug_report" />
+ </LinearLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index c988b4a..eeb64bd8 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -51,15 +51,7 @@
<LinearLayout
android:id="@+id/screenshot_actions"
android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/screenshot_share_chip"/>
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/screenshot_edit_chip"/>
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/screenshot_scroll_chip"
- android:visibility="gone" />
- </LinearLayout>
+ android:layout_height="wrap_content" />
</HorizontalScrollView>
<View
android:id="@+id/screenshot_preview_border"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b95ee56..b0b5482 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1091,7 +1091,7 @@
<dimen name="remote_input_history_extra_height">60dp</dimen>
<!-- Biometric Dialog values -->
- <dimen name="biometric_dialog_face_icon_size">64dp</dimen>
+ <dimen name="biometric_dialog_face_icon_size">54dp</dimen>
<dimen name="biometric_dialog_fingerprint_icon_width">80dp</dimen>
<dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen>
<dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
@@ -1109,6 +1109,22 @@
<dimen name="biometric_dialog_width">240dp</dimen>
<dimen name="biometric_dialog_height">240dp</dimen>
+ <!-- Dimensions for biometric prompt panel padding -->
+ <dimen name="biometric_prompt_small_horizontal_guideline_padding">344dp</dimen>
+ <dimen name="biometric_prompt_udfps_horizontal_guideline_padding">114dp</dimen>
+ <dimen name="biometric_prompt_udfps_mid_guideline_padding">409dp</dimen>
+ <dimen name="biometric_prompt_medium_horizontal_guideline_padding">640dp</dimen>
+ <dimen name="biometric_prompt_medium_mid_guideline_padding">330dp</dimen>
+
+ <!-- Dimensions for biometric prompt icon padding -->
+ <dimen name="biometric_prompt_portrait_small_bottom_padding">60dp</dimen>
+ <dimen name="biometric_prompt_portrait_medium_bottom_padding">160dp</dimen>
+ <dimen name="biometric_prompt_portrait_large_screen_bottom_padding">176dp</dimen>
+ <dimen name="biometric_prompt_landscape_small_bottom_padding">192dp</dimen>
+ <dimen name="biometric_prompt_landscape_small_horizontal_padding">145dp</dimen>
+ <dimen name="biometric_prompt_landscape_medium_bottom_padding">148dp</dimen>
+ <dimen name="biometric_prompt_landscape_medium_horizontal_padding">125dp</dimen>
+
<!-- Dimensions for biometric prompt custom content view. -->
<dimen name="biometric_prompt_logo_size">32dp</dimen>
<dimen name="biometric_prompt_content_corner_radius">28dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2446039..af661aa 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -872,6 +872,8 @@
<string name="qs_record_issue_start">Start</string>
<!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
<string name="qs_record_issue_stop">Stop</string>
+ <!-- QuickSettings: Should user take a bugreport or only share trace files [CHAR LIMIT=20] -->
+ <string name="qs_record_issue_bug_report">Bug Report</string>
<!-- QuickSettings: Issue Type Drop down options in Record Issue Start Dialog [CHAR LIMIT=50] -->
<string name="qs_record_issue_dropdown_header">What part of your device experience was affected?</string>
@@ -1971,7 +1973,7 @@
<!-- Content description for the clear search button in shortcut search list. [CHAR LIMIT=NONE] -->
<string name="keyboard_shortcut_clear_text">Clear search query</string>
<!-- The title for keyboard shortcut search list [CHAR LIMIT=25] -->
- <string name="keyboard_shortcut_search_list_title">Shortcuts</string>
+ <string name="keyboard_shortcut_search_list_title">Keyboard Shortcuts</string>
<!-- The hint for keyboard shortcut search list [CHAR LIMIT=25] -->
<string name="keyboard_shortcut_search_list_hint">Search shortcuts</string>
<!-- The description for no shortcuts results [CHAR LIMIT=25] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 9da4f79..2c9006e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -230,6 +230,8 @@
<style name="TextAppearance.AuthCredential.Indicator" parent="TextAppearance.Material3.BodyMedium">
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:marqueeRepeatLimit">marquee_forever</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:ellipsize">marquee</item>
</style>
<style name="TextAppearance.AuthCredential.Error">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
index 7af9917..d0d5caf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -32,6 +32,8 @@
import dagger.Provides
import java.util.concurrent.Executor
import javax.inject.Singleton
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.android.asCoroutineDispatcher
/**
* Dagger module with system-only dependencies for the unfold animation. The code that is used to
@@ -78,6 +80,13 @@
@Provides
@UnfoldBg
@Singleton
+ fun unfoldBgDispatcher(@UnfoldBg handler: Handler): CoroutineDispatcher {
+ return handler.asCoroutineDispatcher("@UnfoldBg Dispatcher")
+ }
+
+ @Provides
+ @UnfoldBg
+ @Singleton
fun provideBgLooper(): Looper {
return HandlerThread("UnfoldBg", Process.THREAD_PRIORITY_FOREGROUND)
.apply { start() }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 8a18efc..70182c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -544,7 +544,9 @@
step.copy(value = 1f - step.value)
},
keyguardTransitionInteractor.lockscreenToAodTransition,
- )
+ ).filter {
+ it.transitionState != TransitionState.FINISHED
+ }
.collect { handleDoze(it.value) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index ea8fe59..fb88f0e 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -300,7 +300,7 @@
Class<?> cls = entry.getKey();
Dependencies dep = cls.getAnnotation(Dependencies.class);
- Class<? extends CoreStartable>[] deps = (dep == null ? null : dep.value());
+ Class<?>[] deps = (dep == null ? null : dep.value());
if (deps == null || startedStartables.containsAll(Arrays.asList(deps))) {
String clsName = cls.getName();
int i = serviceIndex; // Copied to make lambda happy.
@@ -324,7 +324,7 @@
Map.Entry<Class<?>, Provider<CoreStartable>> entry = nextQueue.removeFirst();
Class<?> cls = entry.getKey();
Dependencies dep = cls.getAnnotation(Dependencies.class);
- Class<? extends CoreStartable>[] deps = (dep == null ? null : dep.value());
+ Class<?>[] deps = (dep == null ? null : dep.value());
StringJoiner stringJoiner = new StringJoiner(", ");
for (int i = 0; deps != null && i < deps.length; i++) {
if (!startedStartables.contains(deps[i])) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 96eb4b3..475bb2c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -43,14 +43,14 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.accessibility.hearingaid.HearingDevicesListAdapter.HearingDeviceItemCallback;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.bluetooth.qsdialog.ActiveHearingDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.AvailableHearingDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory;
+import com.android.systemui.bluetooth.qsdialog.SavedHearingDeviceItemFactory;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.tiles.dialog.bluetooth.ActiveHearingDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.AvailableHearingDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.ConnectedDeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItemFactory;
-import com.android.systemui.qs.tiles.dialog.bluetooth.SavedHearingDeviceItemFactory;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 695d04f..737805b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -27,7 +27,7 @@
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import com.android.systemui.res.R;
import kotlin.Pair;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 8ca083f..5df7fc9 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -243,12 +243,6 @@
}
// Authentication failed.
-
- if (tryAutoConfirm) {
- // Auto-confirm is active, the failed attempt should have no side-effects.
- return AuthenticationResult.FAILED
- }
-
repository.reportAuthenticationAttempt(isSuccessful = false)
if (authenticationResult.lockoutDurationMs > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index b2ade4f..76d46ed 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -288,12 +288,14 @@
// set padding
launch {
viewModel.promptPadding.collect { promptPadding ->
- view.setPadding(
- promptPadding.left,
- promptPadding.top,
- promptPadding.right,
- promptPadding.bottom
- )
+ if (!constraintBp()) {
+ view.setPadding(
+ promptPadding.left,
+ promptPadding.top,
+ promptPadding.right,
+ promptPadding.bottom
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index e3c0cba..f380746 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -20,7 +20,6 @@
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.graphics.Outline
-import android.graphics.Rect
import android.transition.AutoTransition
import android.transition.TransitionManager
import android.util.TypedValue
@@ -47,17 +46,14 @@
import com.android.systemui.biometrics.ui.viewmodel.PromptPosition
import com.android.systemui.biometrics.ui.viewmodel.PromptSize
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
-import com.android.systemui.biometrics.ui.viewmodel.isBottom
import com.android.systemui.biometrics.ui.viewmodel.isLarge
import com.android.systemui.biometrics.ui.viewmodel.isLeft
import com.android.systemui.biometrics.ui.viewmodel.isMedium
import com.android.systemui.biometrics.ui.viewmodel.isNullOrNotSmall
-import com.android.systemui.biometrics.ui.viewmodel.isRight
import com.android.systemui.biometrics.ui.viewmodel.isSmall
-import com.android.systemui.biometrics.ui.viewmodel.isTop
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
-import kotlin.math.min
+import kotlin.math.abs
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -97,8 +93,6 @@
if (constraintBp()) {
val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline)
val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline)
- val bottomGuideline = view.requireViewById<Guideline>(R.id.bottomGuideline)
- val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline)
val midGuideline = view.findViewById<Guideline>(R.id.midGuideline)
val iconHolderView = view.requireViewById<View>(R.id.biometric_icon)
@@ -121,165 +115,12 @@
val largeConstraintSet = ConstraintSet()
largeConstraintSet.clone(mediumConstraintSet)
+ largeConstraintSet.constrainMaxWidth(R.id.panel, view.width)
// TODO: Investigate better way to handle 180 rotations
val flipConstraintSet = ConstraintSet()
- flipConstraintSet.clone(mediumConstraintSet)
- flipConstraintSet.connect(
- R.id.scrollView,
- ConstraintSet.START,
- R.id.midGuideline,
- ConstraintSet.START
- )
- flipConstraintSet.connect(
- R.id.scrollView,
- ConstraintSet.END,
- R.id.rightGuideline,
- ConstraintSet.END
- )
- flipConstraintSet.setHorizontalBias(R.id.biometric_icon, .2f)
-
- // Round the panel outline
- panelView.outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(
- 0,
- 0,
- view.width,
- view.height + cornerRadiusPx,
- cornerRadiusPx.toFloat()
- )
- }
- }
view.doOnLayout {
- val windowBounds = windowManager.maximumWindowMetrics.bounds
- val bottomInset =
- windowManager.maximumWindowMetrics.windowInsets
- .getInsets(WindowInsets.Type.navigationBars())
- .bottom
-
- // TODO: Move to viewmodel
- fun measureBounds(position: PromptPosition) {
- val density = windowManager.currentWindowMetrics.density
- val width = min((640 * density).toInt(), windowBounds.width())
-
- var left = -1
- var right = -1
- var bottom = -1
- var mid = -1
-
- when {
- position.isTop -> {
- // Round bottom corners
- panelView.outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(
- 0,
- -cornerRadiusPx,
- view.width,
- view.height,
- cornerRadiusPx.toFloat()
- )
- }
- }
- left = windowBounds.centerX() - width / 2 + viewModel.promptMargin
- right = windowBounds.centerX() - width / 2 + viewModel.promptMargin
- bottom = iconHolderView.centerY() * 2 - iconHolderView.centerY() / 4
- }
- position.isBottom -> {
- // Round top corners
- panelView.outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(
- 0,
- 0,
- view.width,
- view.height + cornerRadiusPx,
- cornerRadiusPx.toFloat()
- )
- }
- }
-
- left = windowBounds.centerX() - width / 2
- right = windowBounds.centerX() - width / 2
- bottom = if (view.isLandscape()) bottomInset else 0
- }
- position.isLeft -> {
- // Round right corners
- panelView.outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(
- -cornerRadiusPx,
- 0,
- view.width,
- view.height,
- cornerRadiusPx.toFloat()
- )
- }
- }
-
- left = 0
- mid = (windowBounds.width() * .85).toInt() / 2
- right = windowBounds.width() - (windowBounds.width() * .85).toInt()
- bottom = if (view.isLandscape()) bottomInset else 0
- }
- position.isRight -> {
- // Round left corners
- panelView.outlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(
- 0,
- 0,
- view.width + cornerRadiusPx,
- view.height,
- cornerRadiusPx.toFloat()
- )
- }
- }
-
- left = windowBounds.width() - (windowBounds.width() * .85).toInt()
- right = 0
- bottom = if (view.isLandscape()) bottomInset else 0
- mid = windowBounds.width() - (windowBounds.width() * .85).toInt() / 2
- }
- }
-
- val bounds = Rect(left, mid, right, bottom)
- if (bounds.shouldAdjustLeftGuideline()) {
- leftGuideline.setGuidelineBegin(bounds.left)
- smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
- mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
- }
- if (bounds.shouldAdjustRightGuideline()) {
- rightGuideline.setGuidelineEnd(bounds.right)
- smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
- mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
- }
- if (bounds.shouldAdjustBottomGuideline()) {
- bottomGuideline.setGuidelineEnd(bounds.bottom)
- smallConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
- mediumConstraintSet.setGuidelineEnd(bottomGuideline.id, bounds.bottom)
- }
-
- if (position.isBottom) {
- topGuideline.setGuidelinePercent(.25f)
- mediumConstraintSet.setGuidelinePercent(topGuideline.id, .25f)
- } else {
- topGuideline.setGuidelinePercent(0f)
- mediumConstraintSet.setGuidelinePercent(topGuideline.id, 0f)
- }
-
- if (mid != -1 && midGuideline != null) {
- midGuideline.setGuidelineBegin(mid)
- }
- }
-
fun setVisibilities(size: PromptSize) {
viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) }
largeConstraintSet.setVisibility(iconHolderView.id, View.GONE)
@@ -297,36 +138,151 @@
}
}
+ fun roundCorners(size: PromptSize, position: PromptPosition) {
+ var left = 0
+ var top = 0
+ var right = 0
+ var bottom = 0
+ when (size) {
+ PromptSize.SMALL,
+ PromptSize.MEDIUM ->
+ when (position) {
+ PromptPosition.Right -> {
+ left = 0
+ top = 0
+ right = view.width + cornerRadiusPx
+ bottom = view.height
+ }
+ PromptPosition.Left -> {
+ left = -cornerRadiusPx
+ top = 0
+ right = view.width
+ bottom = view.height
+ }
+ PromptPosition.Top -> {
+ left = 0
+ top = -cornerRadiusPx
+ right = panelView.width
+ bottom = view.height
+ }
+ PromptPosition.Bottom -> {
+ left = 0
+ top = 0
+ right = panelView.width
+ bottom = view.height + cornerRadiusPx
+ }
+ }
+ PromptSize.LARGE -> {
+ left = 0
+ top = 0
+ right = view.width
+ bottom = view.height
+ }
+ }
+
+ // Round the panel outline
+ panelView.outlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(
+ left,
+ top,
+ right,
+ bottom,
+ cornerRadiusPx.toFloat()
+ )
+ }
+ }
+ }
+
view.repeatWhenAttached {
var currentSize: PromptSize? = null
lifecycleScope.launch {
+ viewModel.guidelineBounds.collect { bounds ->
+ if (bounds.left >= 0) {
+ mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+ smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left)
+ } else if (bounds.left < 0) {
+ mediumConstraintSet.setGuidelineEnd(
+ leftGuideline.id,
+ abs(bounds.left)
+ )
+ smallConstraintSet.setGuidelineEnd(
+ leftGuideline.id,
+ abs(bounds.left)
+ )
+ }
+
+ if (bounds.right >= 0) {
+ mediumConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+ smallConstraintSet.setGuidelineEnd(rightGuideline.id, bounds.right)
+ } else if (bounds.right < 0) {
+ mediumConstraintSet.setGuidelineBegin(
+ rightGuideline.id,
+ abs(bounds.right)
+ )
+ smallConstraintSet.setGuidelineBegin(
+ rightGuideline.id,
+ abs(bounds.right)
+ )
+ }
+
+ if (midGuideline != null) {
+ if (bounds.bottom >= 0) {
+ midGuideline.setGuidelineEnd(bounds.bottom)
+ mediumConstraintSet.setGuidelineEnd(
+ midGuideline.id,
+ bounds.bottom
+ )
+ } else if (bounds.bottom < 0) {
+ midGuideline.setGuidelineBegin(abs(bounds.bottom))
+ mediumConstraintSet.setGuidelineBegin(
+ midGuideline.id,
+ abs(bounds.bottom)
+ )
+ }
+ }
+ }
+ }
+
+ lifecycleScope.launch {
combine(viewModel.position, viewModel.size, ::Pair).collect {
(position, size) ->
view.doOnAttach {
+ setVisibilities(size)
+
if (position.isLeft) {
- flipConstraintSet.applyTo(view)
- } else if (position.isRight) {
- mediumConstraintSet.applyTo(view)
+ if (size.isSmall) {
+ flipConstraintSet.clone(smallConstraintSet)
+ } else {
+ flipConstraintSet.clone(mediumConstraintSet)
+ }
+
+ // Move all content to other panel
+ flipConstraintSet.connect(
+ R.id.scrollView,
+ ConstraintSet.START,
+ R.id.midGuideline,
+ ConstraintSet.START
+ )
+ flipConstraintSet.connect(
+ R.id.scrollView,
+ ConstraintSet.END,
+ R.id.rightGuideline,
+ ConstraintSet.END
+ )
}
- measureBounds(position)
- setVisibilities(size)
+ roundCorners(size, position)
+
when {
size.isSmall -> {
- val ratio =
- if (view.isLandscape()) {
- (windowBounds.height() -
- bottomInset -
- viewModel.promptMargin)
- .toFloat() / windowBounds.height()
- } else {
- (windowBounds.height() - viewModel.promptMargin)
- .toFloat() / windowBounds.height()
- }
- smallConstraintSet.setVerticalBias(iconHolderView.id, ratio)
-
- smallConstraintSet.applyTo(view as ConstraintLayout?)
+ if (position.isLeft) {
+ flipConstraintSet.applyTo(view)
+ } else {
+ smallConstraintSet.applyTo(view)
+ }
}
size.isMedium && currentSize.isSmall -> {
val autoTransition = AutoTransition()
@@ -338,7 +294,19 @@
view,
autoTransition
)
- mediumConstraintSet.applyTo(view)
+
+ if (position.isLeft) {
+ flipConstraintSet.applyTo(view)
+ } else {
+ mediumConstraintSet.applyTo(view)
+ }
+ }
+ size.isMedium -> {
+ if (position.isLeft) {
+ flipConstraintSet.applyTo(view)
+ } else {
+ mediumConstraintSet.applyTo(view)
+ }
}
size.isLarge -> {
val autoTransition = AutoTransition()
@@ -551,20 +519,6 @@
}
}
-private fun View.centerX(): Int {
- return (x + width / 2).toInt()
-}
-
-private fun View.centerY(): Int {
- return (y + height / 2).toInt()
-}
-
-private fun Rect.shouldAdjustLeftGuideline(): Boolean = left != -1
-
-private fun Rect.shouldAdjustRightGuideline(): Boolean = right != -1
-
-private fun Rect.shouldAdjustBottomGuideline(): Boolean = bottom != -1
-
private fun View.asVerticalAnimator(
duration: Long,
toY: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index d8265c7..66b7d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -37,7 +37,6 @@
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
/** Sub-binder for [BiometricPromptLayout.iconView]. */
@@ -127,14 +126,22 @@
if (constraintBp() && position != Rect()) {
val iconParams = iconView.layoutParams as ConstraintLayout.LayoutParams
- if (position.left != -1) {
+ if (position.left != 0) {
iconParams.endToEnd = ConstraintSet.UNSET
iconParams.leftMargin = position.left
}
- if (position.top != -1) {
+ if (position.top != 0) {
iconParams.bottomToBottom = ConstraintSet.UNSET
iconParams.topMargin = position.top
}
+ if (position.right != 0) {
+ iconParams.startToStart = ConstraintSet.UNSET
+ iconParams.rightMargin = position.right
+ }
+ if (position.bottom != 0) {
+ iconParams.topToTop = ConstraintSet.UNSET
+ iconParams.bottomMargin = position.bottom
+ }
iconView.layoutParams = iconParams
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
index d0c140b..8dbed5f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -94,25 +94,76 @@
params.naturalDisplayHeight,
rotation.ordinal
)
- rotatedBounds
+ Rect(
+ rotatedBounds.left,
+ rotatedBounds.top,
+ params.logicalDisplayWidth - rotatedBounds.right,
+ params.logicalDisplayHeight - rotatedBounds.bottom
+ )
}
.distinctUntilChanged()
val iconPosition: Flow<Rect> =
- combine(udfpsSensorBounds, promptViewModel.size, promptViewModel.modalities) {
- sensorBounds,
- size,
- modalities ->
- // If not Udfps, icon does not change from default layout position
- if (!modalities.hasUdfps) {
- Rect() // Empty rect, don't offset from default position
- } else if (size.isSmall) {
- // When small with Udfps, only set horizontal position
- Rect(sensorBounds.left, -1, sensorBounds.right, -1)
- } else {
- sensorBounds
+ combine(
+ udfpsSensorBounds,
+ promptViewModel.size,
+ promptViewModel.position,
+ promptViewModel.modalities
+ ) { sensorBounds, size, position, modalities ->
+ when (position) {
+ PromptPosition.Bottom ->
+ if (size.isSmall) {
+ Rect(0, 0, 0, promptViewModel.portraitSmallBottomPadding)
+ } else if (size.isMedium && modalities.hasUdfps) {
+ Rect(0, 0, 0, sensorBounds.bottom)
+ } else if (size.isMedium) {
+ Rect(0, 0, 0, promptViewModel.portraitMediumBottomPadding)
+ } else {
+ // Large screen
+ Rect(0, 0, 0, promptViewModel.portraitLargeScreenBottomPadding)
+ }
+ PromptPosition.Right ->
+ if (size.isSmall || modalities.hasFaceOnly) {
+ Rect(
+ 0,
+ 0,
+ promptViewModel.landscapeSmallHorizontalPadding,
+ promptViewModel.landscapeSmallBottomPadding
+ )
+ } else if (size.isMedium && modalities.hasUdfps) {
+ Rect(0, 0, sensorBounds.right, sensorBounds.bottom)
+ } else {
+ // SFPS
+ Rect(
+ 0,
+ 0,
+ promptViewModel.landscapeMediumHorizontalPadding,
+ promptViewModel.landscapeMediumBottomPadding
+ )
+ }
+ PromptPosition.Left ->
+ if (size.isSmall || modalities.hasFaceOnly) {
+ Rect(
+ promptViewModel.landscapeSmallHorizontalPadding,
+ 0,
+ 0,
+ promptViewModel.landscapeSmallBottomPadding
+ )
+ } else if (size.isMedium && modalities.hasUdfps) {
+ Rect(sensorBounds.left, 0, 0, sensorBounds.bottom)
+ } else {
+ // SFPS
+ Rect(
+ promptViewModel.landscapeMediumHorizontalPadding,
+ 0,
+ 0,
+ promptViewModel.landscapeMediumBottomPadding
+ )
+ }
+ PromptPosition.Top -> Rect()
+ }
}
- }
+ .distinctUntilChanged()
/** Whether an error message is currently being shown. */
val showingError = promptViewModel.showingError
@@ -162,10 +213,11 @@
val iconSize: Flow<Pair<Int, Int>> =
combine(
+ promptViewModel.position,
activeAuthType,
promptViewModel.fingerprintSensorWidth,
promptViewModel.fingerprintSensorHeight,
- ) { activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight ->
+ ) { _, activeAuthType, fingerprintSensorWidth, fingerprintSensorHeight ->
if (activeAuthType == AuthType.Face) {
Pair(promptViewModel.faceIconWidth, promptViewModel.faceIconHeight)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index fbd87fd..21ebff4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -86,6 +86,36 @@
val faceIconHeight: Int =
context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+ /** Padding for placing icons */
+ val portraitSmallBottomPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_portrait_small_bottom_padding
+ )
+ val portraitMediumBottomPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_portrait_medium_bottom_padding
+ )
+ val portraitLargeScreenBottomPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_portrait_large_screen_bottom_padding
+ )
+ val landscapeSmallBottomPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_landscape_small_bottom_padding
+ )
+ val landscapeSmallHorizontalPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_landscape_small_horizontal_padding
+ )
+ val landscapeMediumBottomPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_landscape_medium_bottom_padding
+ )
+ val landscapeMediumHorizontalPadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_landscape_medium_horizontal_padding
+ )
+
val fingerprintSensorWidth: Flow<Int> =
combine(modalities, udfpsOverlayInteractor.udfpsOverlayParams) { modalities, overlayParams
->
@@ -111,9 +141,6 @@
/** Hint for talkback directional guidance */
val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow()
- val promptMargin: Int =
- context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
-
private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
/** If the user is currently authenticating (i.e. at least one biometric is scanning). */
@@ -205,6 +232,66 @@
}
.distinctUntilChanged()
+ /** Prompt panel size padding */
+ private val smallHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_small_horizontal_guideline_padding
+ )
+ private val udfpsHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_udfps_horizontal_guideline_padding
+ )
+ private val udfpsMidGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_udfps_mid_guideline_padding
+ )
+ private val mediumHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_medium_horizontal_guideline_padding
+ )
+ private val mediumMidGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_medium_mid_guideline_padding
+ )
+
+ /**
+ * Rect for positioning prompt guidelines (left, top, right, mid)
+ *
+ * Negative values are used to signify that guideline measuring should be flipped, measuring
+ * from opposite side of the screen
+ */
+ val guidelineBounds: Flow<Rect> =
+ combine(size, position, modalities) { size, position, modalities ->
+ if (position.isBottom) {
+ Rect(0, 0, 0, 0)
+ } else if (position.isRight) {
+ if (size.isSmall) {
+ Rect(-smallHorizontalGuidelinePadding, 0, 0, 0)
+ } else if (modalities.hasUdfps) {
+ Rect(udfpsHorizontalGuidelinePadding, 0, 0, udfpsMidGuidelinePadding)
+ } else if (modalities.isEmpty) {
+ // TODO: Temporary fix until no biometric landscape layout is added
+ Rect(-mediumHorizontalGuidelinePadding, 0, 0, 6)
+ } else {
+ Rect(-mediumHorizontalGuidelinePadding, 0, 0, mediumMidGuidelinePadding)
+ }
+ } else if (position.isLeft) {
+ if (size.isSmall) {
+ Rect(0, 0, -smallHorizontalGuidelinePadding, 0)
+ } else if (modalities.hasUdfps) {
+ Rect(0, 0, udfpsHorizontalGuidelinePadding, -udfpsMidGuidelinePadding)
+ } else if (modalities.isEmpty) {
+ // TODO: Temporary fix until no biometric landscape layout is added
+ Rect(0, 0, -mediumHorizontalGuidelinePadding, -6)
+ } else {
+ Rect(0, 0, -mediumHorizontalGuidelinePadding, -mediumMidGuidelinePadding)
+ }
+ } else {
+ Rect()
+ }
+ }
+ .distinctUntilChanged()
+
/**
* If the API caller or the user's personal preferences require explicit confirmation after
* successful authentication. Confirmation always required when in explicit flow.
@@ -424,7 +511,7 @@
isAuthenticated,
promptSelectorInteractor.isCredentialAllowed,
) { size, _, authState, credentialAllowed ->
- size.isNotSmall && authState.isNotAuthenticated && credentialAllowed
+ size.isMedium && authState.isNotAuthenticated && credentialAllowed
}
private val history = PromptHistoryImpl()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
index cfda75c..b72b1f3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -28,6 +28,7 @@
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.Utils
import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -155,17 +156,19 @@
->
val topLeft = Point(sensorLocation.left, sensorLocation.top)
- if (sensorLocation.isSensorVerticalInDefaultOrientation) {
- if (displayRotation == DisplayRotation.ROTATION_0) {
- topLeft.x -= bounds!!.width()
- } else if (displayRotation == DisplayRotation.ROTATION_270) {
- topLeft.y -= bounds!!.height()
- }
- } else {
- if (displayRotation == DisplayRotation.ROTATION_180) {
- topLeft.y -= bounds!!.height()
- } else if (displayRotation == DisplayRotation.ROTATION_270) {
- topLeft.x -= bounds!!.width()
+ if (!constraintBp()) {
+ if (sensorLocation.isSensorVerticalInDefaultOrientation) {
+ if (displayRotation == DisplayRotation.ROTATION_0) {
+ topLeft.x -= bounds!!.width()
+ } else if (displayRotation == DisplayRotation.ROTATION_270) {
+ topLeft.y -= bounds!!.height()
+ }
+ } else {
+ if (displayRotation == DisplayRotation.ROTATION_180) {
+ topLeft.y -= bounds!!.height()
+ } else if (displayRotation == DisplayRotation.ROTATION_270) {
+ topLeft.x -= bounds!!.width()
+ }
}
}
defaultOverlayViewParams.apply {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt
index 59fc81c..f86cad5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractor.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
index 9ee582a..81fe2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.util.Log
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 9c63a30..94d7af7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothAdapter.STATE_OFF
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index a8d9e78..c7d171d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.os.Bundle
import android.view.LayoutInflater
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
index 5d18dc1..c30aea0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.DEBUG
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
index ea51bee..6e51915 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import com.android.settingslib.bluetooth.CachedBluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index cd52e0d..add1647 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index fd624d2..e65b657 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.content.Intent
import android.content.SharedPreferences
@@ -31,15 +31,15 @@
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index 1c621b8..dc5efef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -30,7 +30,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.graphics.drawable.Drawable
import com.android.settingslib.bluetooth.CachedBluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 56ba079..f04ba75 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
import android.content.Context
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index fce25ec..4e28caf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 62da5c0..12cac92 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -99,7 +99,7 @@
.map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
.stateIn(
scope = viewModelScope,
- started = SharingStarted.Eagerly,
+ started = SharingStarted.WhileSubscribed(),
initialValue = ActionButtonAppearance.Hidden,
)
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 357eca3..d2caefd 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -398,6 +398,7 @@
|| mAccessibilityManager.isTouchExplorationEnabled()
|| mDataProvider.isA11yAction()
|| mDataProvider.isFromTrackpad()
+ || mDataProvider.isFromKeyboard()
|| (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED)
&& mDataProvider.isUnfolded());
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index a79a654..76b228d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -16,6 +16,7 @@
package com.android.systemui.classifier;
+import android.view.KeyEvent;
import android.view.MotionEvent;
/**
@@ -50,6 +51,14 @@
void onBouncerHidden();
/**
+ * Call this to record a KeyEvent in the {@link com.android.systemui.plugins.FalsingManager}.
+ *
+ * This may decide to only collect certain KeyEvents and ignore others. Do not assume all
+ * KeyEvents are collected.
+ */
+ void onKeyEvent(KeyEvent ev);
+
+ /**
* Call this to record a MotionEvent in the {@link com.android.systemui.plugins.FalsingManager}.
*
* Be sure to call {@link #onMotionEventComplete()} after the rest of SystemUI is done with the
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index d6b9a11..dcd4195 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -16,6 +16,7 @@
package com.android.systemui.classifier;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import javax.inject.Inject;
@@ -23,6 +24,8 @@
/** */
public class FalsingCollectorFake implements FalsingCollector {
+ public KeyEvent lastKeyEvent = null;
+
@Override
public void init() {
}
@@ -70,6 +73,11 @@
}
@Override
+ public void onKeyEvent(KeyEvent ev) {
+ lastKeyEvent = ev;
+ }
+
+ @Override
public void onTouchEvent(MotionEvent ev) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 4f4f3d0..beaa170 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -21,6 +21,7 @@
import android.hardware.SensorManager;
import android.hardware.biometrics.BiometricSourceType;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.annotation.VisibleForTesting;
@@ -49,7 +50,10 @@
import dagger.Lazy;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
import javax.inject.Inject;
@@ -61,6 +65,14 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final long GESTURE_PROCESSING_DELAY_MS = 100;
+ private final Set<Integer> mAcceptedKeycodes = new HashSet<>(Arrays.asList(
+ KeyEvent.KEYCODE_ENTER,
+ KeyEvent.KEYCODE_ESCAPE,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_SHIFT_RIGHT,
+ KeyEvent.KEYCODE_SPACE
+ ));
+
private final FalsingDataProvider mFalsingDataProvider;
private final FalsingManager mFalsingManager;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -279,6 +291,14 @@
}
@Override
+ public void onKeyEvent(KeyEvent ev) {
+ // Only collect if it is an ACTION_UP action and is allow-listed
+ if (ev.getAction() == KeyEvent.ACTION_UP && mAcceptedKeycodes.contains(ev.getKeyCode())) {
+ mFalsingDataProvider.onKeyEvent(ev);
+ }
+ }
+
+ @Override
public void onTouchEvent(MotionEvent ev) {
logDebug("REAL: onTouchEvent(" + ev.getActionMasked() + ")");
if (!mKeyguardStateController.isShowing()) {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
index c5d8c79..b289fa4 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt
@@ -16,6 +16,7 @@
package com.android.systemui.classifier
+import android.view.KeyEvent
import android.view.MotionEvent
import com.android.systemui.classifier.FalsingCollectorImpl.logDebug
import com.android.systemui.dagger.SysUISingleton
@@ -59,6 +60,10 @@
logDebug("NOOP: onBouncerHidden")
}
+ override fun onKeyEvent(ev: KeyEvent) {
+ logDebug("NOOP: onKeyEvent(${ev.action}")
+ }
+
override fun onTouchEvent(ev: MotionEvent) {
logDebug("NOOP: onTouchEvent(${ev.actionMasked})")
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 809d5b2..1501701 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -20,6 +20,7 @@
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.util.DisplayMetrics;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
@@ -41,6 +42,7 @@
public class FalsingDataProvider {
private static final long MOTION_EVENT_AGE_MS = 1000;
+ private static final long KEY_EVENT_AGE_MS = 500;
private static final long DROP_EVENT_THRESHOLD_MS = 50;
private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI);
@@ -56,8 +58,10 @@
private final List<MotionEventListener> mMotionEventListeners = new ArrayList<>();
private final List<GestureFinalizedListener> mGestureFinalizedListeners = new ArrayList<>();
- private TimeLimitedMotionEventBuffer mRecentMotionEvents =
- new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+ private TimeLimitedInputEventBuffer<MotionEvent> mRecentMotionEvents =
+ new TimeLimitedInputEventBuffer<>(MOTION_EVENT_AGE_MS);
+ private final TimeLimitedInputEventBuffer<KeyEvent> mRecentKeyEvents =
+ new TimeLimitedInputEventBuffer<>(KEY_EVENT_AGE_MS);
private List<MotionEvent> mPriorMotionEvents = new ArrayList<>();
private boolean mDirty = true;
@@ -89,6 +93,10 @@
FalsingClassifier.logInfo("width, height: " + getWidthPixels() + ", " + getHeightPixels());
}
+ void onKeyEvent(KeyEvent keyEvent) {
+ mRecentKeyEvents.add(keyEvent);
+ }
+
void onMotionEvent(MotionEvent motionEvent) {
List<MotionEvent> motionEvents = unpackMotionEvent(motionEvent);
FalsingClassifier.logVerbose("Unpacked into: " + motionEvents.size());
@@ -109,6 +117,10 @@
// previous ACTION_MOVE event and when it happens, it makes some classifiers fail.
mDropLastEvent = shouldDropEvent(motionEvent);
+ if (!motionEvents.isEmpty() && !mRecentKeyEvents.isEmpty()) {
+ recycleAndClearRecentKeyEvents();
+ }
+
mRecentMotionEvents.addAll(motionEvents);
FalsingClassifier.logVerbose("Size: " + mRecentMotionEvents.size());
@@ -141,7 +153,7 @@
mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime()));
mPriorMotionEvents = mRecentMotionEvents;
- mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
+ mRecentMotionEvents = new TimeLimitedInputEventBuffer<>(MOTION_EVENT_AGE_MS);
}
mDropLastEvent = false;
mA11YAction = false;
@@ -261,6 +273,13 @@
return mLastMotionEvent.getY() < mFirstRecentMotionEvent.getY();
}
+ /**
+ * If it's a specific set of keys as collected from {@link FalsingCollector}
+ */
+ public boolean isFromKeyboard() {
+ return !mRecentKeyEvents.isEmpty();
+ }
+
public boolean isFromTrackpad() {
if (mRecentMotionEvents.isEmpty()) {
return false;
@@ -318,6 +337,14 @@
}
}
+ private void recycleAndClearRecentKeyEvents() {
+ for (KeyEvent ev : mRecentKeyEvents) {
+ ev.recycle();
+ }
+
+ mRecentKeyEvents.clear();
+ }
+
private List<MotionEvent> unpackMotionEvent(MotionEvent motionEvent) {
List<MotionEvent> motionEvents = new ArrayList<>();
List<PointerProperties> pointerPropertiesList = new ArrayList<>();
@@ -416,6 +443,8 @@
mRecentMotionEvents.clear();
+ recycleAndClearRecentKeyEvents();
+
mDirty = true;
mSessionListeners.forEach(SessionListener::onSessionEnded);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java
new file mode 100644
index 0000000..7627ad1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedInputEventBuffer.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier;
+
+import android.view.InputEvent;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Maintains an ordered list of the last N milliseconds of InputEvents.
+ *
+ * This class is simply a convenience class designed to look like a simple list, but that
+ * automatically discards old InputEvents. It functions much like a queue - first in first out -
+ * but does not have a fixed size like a circular buffer.
+ */
+public class TimeLimitedInputEventBuffer<T extends InputEvent> implements List<T> {
+
+ private final List<T> mInputEvents;
+ private final long mMaxAgeMs;
+
+ public TimeLimitedInputEventBuffer(long maxAgeMs) {
+ super();
+ mMaxAgeMs = maxAgeMs;
+ mInputEvents = new ArrayList<>();
+ }
+
+ private void ejectOldEvents() {
+ if (mInputEvents.isEmpty()) {
+ return;
+ }
+ Iterator<T> iter = listIterator();
+ long mostRecentMs = mInputEvents.get(mInputEvents.size() - 1).getEventTime();
+ while (iter.hasNext()) {
+ T ev = iter.next();
+ if (mostRecentMs - ev.getEventTime() > mMaxAgeMs) {
+ iter.remove();
+ ev.recycle();
+ }
+ }
+ }
+
+ @Override
+ public void add(int index, T element) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T remove(int index) {
+ return mInputEvents.remove(index);
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return mInputEvents.indexOf(o);
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return mInputEvents.lastIndexOf(o);
+ }
+
+ @Override
+ public int size() {
+ return mInputEvents.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return mInputEvents.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return mInputEvents.contains(o);
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return mInputEvents.iterator();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return mInputEvents.toArray();
+ }
+
+ @Override
+ public <T2> T2[] toArray(T2[] a) {
+ return mInputEvents.toArray(a);
+ }
+
+ @Override
+ public boolean add(T element) {
+ boolean result = mInputEvents.add(element);
+ ejectOldEvents();
+ return result;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ return mInputEvents.remove(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return mInputEvents.containsAll(c);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends T> collection) {
+ boolean result = mInputEvents.addAll(collection);
+ ejectOldEvents();
+ return result;
+ }
+
+ @Override
+ public boolean addAll(int index, Collection<? extends T> elements) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ return mInputEvents.removeAll(c);
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ return mInputEvents.retainAll(c);
+ }
+
+ @Override
+ public void clear() {
+ mInputEvents.clear();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return mInputEvents.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return mInputEvents.hashCode();
+ }
+
+ @Override
+ public T get(int index) {
+ return mInputEvents.get(index);
+ }
+
+ @Override
+ public T set(int index, T element) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ListIterator<T> listIterator() {
+ return new Iter(0);
+ }
+
+ @Override
+ public ListIterator<T> listIterator(int index) {
+ return new Iter(index);
+ }
+
+ @Override
+ public List<T> subList(int fromIndex, int toIndex) {
+ return mInputEvents.subList(fromIndex, toIndex);
+ }
+
+ class Iter implements ListIterator<T> {
+
+ private final ListIterator<T> mIterator;
+
+ Iter(int index) {
+ this.mIterator = mInputEvents.listIterator(index);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return mIterator.hasNext();
+ }
+
+ @Override
+ public T next() {
+ return mIterator.next();
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return mIterator.hasPrevious();
+ }
+
+ @Override
+ public T previous() {
+ return mIterator.previous();
+ }
+
+ @Override
+ public int nextIndex() {
+ return mIterator.nextIndex();
+ }
+
+ @Override
+ public int previousIndex() {
+ return mIterator.previousIndex();
+ }
+
+ @Override
+ public void remove() {
+ mIterator.remove();
+ }
+
+ @Override
+ public void set(T inputEvent) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void add(T element) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
deleted file mode 100644
index 51aede7..0000000
--- a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.classifier;
-
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-
-/**
- * Maintains an ordered list of the last N milliseconds of MotionEvents.
- *
- * This class is simply a convenience class designed to look like a simple list, but that
- * automatically discards old MotionEvents. It functions much like a queue - first in first out -
- * but does not have a fixed size like a circular buffer.
- */
-public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
-
- private final List<MotionEvent> mMotionEvents;
- private final long mMaxAgeMs;
-
- public TimeLimitedMotionEventBuffer(long maxAgeMs) {
- super();
- mMaxAgeMs = maxAgeMs;
- mMotionEvents = new ArrayList<>();
- }
-
- private void ejectOldEvents() {
- if (mMotionEvents.isEmpty()) {
- return;
- }
- Iterator<MotionEvent> iter = listIterator();
- long mostRecentMs = mMotionEvents.get(mMotionEvents.size() - 1).getEventTime();
- while (iter.hasNext()) {
- MotionEvent ev = iter.next();
- if (mostRecentMs - ev.getEventTime() > mMaxAgeMs) {
- iter.remove();
- ev.recycle();
- }
- }
- }
-
- @Override
- public void add(int index, MotionEvent element) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public MotionEvent remove(int index) {
- return mMotionEvents.remove(index);
- }
-
- @Override
- public int indexOf(Object o) {
- return mMotionEvents.indexOf(o);
- }
-
- @Override
- public int lastIndexOf(Object o) {
- return mMotionEvents.lastIndexOf(o);
- }
-
- @Override
- public int size() {
- return mMotionEvents.size();
- }
-
- @Override
- public boolean isEmpty() {
- return mMotionEvents.isEmpty();
- }
-
- @Override
- public boolean contains(Object o) {
- return mMotionEvents.contains(o);
- }
-
- @Override
- public Iterator<MotionEvent> iterator() {
- return mMotionEvents.iterator();
- }
-
- @Override
- public Object[] toArray() {
- return mMotionEvents.toArray();
- }
-
- @Override
- public <T> T[] toArray(T[] a) {
- return mMotionEvents.toArray(a);
- }
-
- @Override
- public boolean add(MotionEvent element) {
- boolean result = mMotionEvents.add(element);
- ejectOldEvents();
- return result;
- }
-
- @Override
- public boolean remove(Object o) {
- return mMotionEvents.remove(o);
- }
-
- @Override
- public boolean containsAll(Collection<?> c) {
- return mMotionEvents.containsAll(c);
- }
-
- @Override
- public boolean addAll(Collection<? extends MotionEvent> collection) {
- boolean result = mMotionEvents.addAll(collection);
- ejectOldEvents();
- return result;
- }
-
- @Override
- public boolean addAll(int index, Collection<? extends MotionEvent> elements) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean removeAll(Collection<?> c) {
- return mMotionEvents.removeAll(c);
- }
-
- @Override
- public boolean retainAll(Collection<?> c) {
- return mMotionEvents.retainAll(c);
- }
-
- @Override
- public void clear() {
- mMotionEvents.clear();
- }
-
- @Override
- public boolean equals(Object o) {
- return mMotionEvents.equals(o);
- }
-
- @Override
- public int hashCode() {
- return mMotionEvents.hashCode();
- }
-
- @Override
- public MotionEvent get(int index) {
- return mMotionEvents.get(index);
- }
-
- @Override
- public MotionEvent set(int index, MotionEvent element) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public ListIterator<MotionEvent> listIterator() {
- return new Iter(0);
- }
-
- @Override
- public ListIterator<MotionEvent> listIterator(int index) {
- return new Iter(index);
- }
-
- @Override
- public List<MotionEvent> subList(int fromIndex, int toIndex) {
- return mMotionEvents.subList(fromIndex, toIndex);
- }
-
- class Iter implements ListIterator<MotionEvent> {
-
- private final ListIterator<MotionEvent> mIterator;
-
- Iter(int index) {
- this.mIterator = mMotionEvents.listIterator(index);
- }
-
- @Override
- public boolean hasNext() {
- return mIterator.hasNext();
- }
-
- @Override
- public MotionEvent next() {
- return mIterator.next();
- }
-
- @Override
- public boolean hasPrevious() {
- return mIterator.hasPrevious();
- }
-
- @Override
- public MotionEvent previous() {
- return mIterator.previous();
- }
-
- @Override
- public int nextIndex() {
- return mIterator.nextIndex();
- }
-
- @Override
- public int previousIndex() {
- return mIterator.previousIndex();
- }
-
- @Override
- public void remove() {
- mIterator.remove();
- }
-
- @Override
- public void set(MotionEvent motionEvent) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void add(MotionEvent element) {
- throw new UnsupportedOperationException();
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
index 7dd883b..f7ea25c 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/ViewExt.kt
@@ -46,8 +46,25 @@
}
/** Adds a [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */
-fun View.onLayoutChanged(onLayoutChanged: (v: View) -> Unit): DisposableHandle {
- val listener = View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ -> onLayoutChanged(v) }
+fun View.onLayoutChanged(onLayoutChanged: (v: View) -> Unit): DisposableHandle =
+ onLayoutChanged { v, _, _, _, _, _, _, _, _ ->
+ onLayoutChanged(v)
+ }
+
+/** Adds the [View.OnLayoutChangeListener] and provides a [DisposableHandle] for teardown. */
+fun View.onLayoutChanged(listener: View.OnLayoutChangeListener): DisposableHandle {
addOnLayoutChangeListener(listener)
return DisposableHandle { removeOnLayoutChangeListener(listener) }
}
+
+/** Adds a [View.OnApplyWindowInsetsListener] and provides a [DisposableHandle] for teardown. */
+fun View.onApplyWindowInsets(listener: View.OnApplyWindowInsetsListener): DisposableHandle {
+ setOnApplyWindowInsetsListener(listener)
+ return DisposableHandle { setOnApplyWindowInsetsListener(null) }
+}
+
+/** Adds a [View.OnTouchListener] and provides a [DisposableHandle] for teardown. */
+fun View.onTouchListener(listener: View.OnTouchListener): DisposableHandle {
+ setOnTouchListener(listener)
+ return DisposableHandle { setOnTouchListener(null) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index baae986..0e04d15 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -40,7 +40,6 @@
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
@@ -313,11 +312,7 @@
// or device starts going to sleep.
merge(
powerInteractor.isAsleep,
- if (KeyguardWmStateRefactor.isEnabled) {
- keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
- } else {
- keyguardRepository.keyguardDoneAnimationsFinished.map { true }
- },
+ keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE),
userRepository.selectedUser.map {
it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
},
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
index 1382468..486320a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt
@@ -34,6 +34,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launch
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.customization.R as customizationR
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
@@ -116,7 +117,7 @@
constrainWidth(R.id.lockscreen_clock_view_large, ConstraintSet.WRAP_CONTENT)
constrainHeight(R.id.lockscreen_clock_view_large, ConstraintSet.MATCH_CONSTRAINT)
val largeClockTopMargin =
- context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+ SystemBarUtils.getStatusBarHeight(context) +
context.resources.getDimensionPixelSize(
customizationR.dimen.small_clock_padding_top
) +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 0249abd..44fd582 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -41,6 +41,9 @@
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.view.onApplyWindowInsets
+import com.android.systemui.common.ui.view.onLayoutChanged
+import com.android.systemui.common.ui.view.onTouchListener
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
@@ -64,6 +67,7 @@
import com.android.systemui.temporarydisplay.ViewPriority
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
+import com.android.systemui.util.kotlin.DisposableHandles
import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
@@ -99,16 +103,19 @@
keyguardViewMediator: KeyguardViewMediator?,
mainImmediateDispatcher: CoroutineDispatcher,
): DisposableHandle {
- var onLayoutChangeListener: OnLayoutChange? = null
+ val disposables = DisposableHandles()
val childViews = mutableMapOf<Int, View>()
if (KeyguardBottomAreaRefactor.isEnabled) {
- view.setOnTouchListener { _, event ->
- if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
- viewModel.setRootViewLastTapPosition(Point(event.x.toInt(), event.y.toInt()))
+ disposables +=
+ view.onTouchListener { _, event ->
+ if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) {
+ viewModel.setRootViewLastTapPosition(
+ Point(event.x.toInt(), event.y.toInt())
+ )
+ }
+ false
}
- false
- }
}
val burnInParams = MutableStateFlow(BurnInParameters())
@@ -117,7 +124,7 @@
alpha = { view.alpha },
)
- val disposableHandle =
+ disposables +=
view.repeatWhenAttached(mainImmediateDispatcher) {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch("$TAG#occludingAppDeviceEntryMessageViewModel.message") {
@@ -346,8 +353,7 @@
}
}
- onLayoutChangeListener = OnLayoutChange(viewModel, childViews, burnInParams)
- view.addOnLayoutChangeListener(onLayoutChangeListener)
+ disposables += view.onLayoutChanged(OnLayoutChange(viewModel, childViews, burnInParams))
// Views will be added or removed after the call to bind(). This is needed to avoid many
// calls to findViewById
@@ -362,24 +368,21 @@
}
}
)
-
- view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
- val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
- burnInParams.update { current ->
- current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
- }
- insets
+ disposables += DisposableHandle {
+ view.setOnHierarchyChangeListener(null)
+ childViews.clear()
}
- return object : DisposableHandle {
- override fun dispose() {
- disposableHandle.dispose()
- view.removeOnLayoutChangeListener(onLayoutChangeListener)
- view.setOnHierarchyChangeListener(null)
- view.setOnApplyWindowInsetsListener(null)
- childViews.clear()
+ disposables +=
+ view.onApplyWindowInsets { _: View, insets: WindowInsets ->
+ val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+ burnInParams.update { current ->
+ current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ }
+ insets
}
- }
+
+ return disposables
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 39df4c3..ce1aed0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -47,6 +47,7 @@
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.core.view.isInvisible
+import com.android.internal.policy.SystemBarUtils
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.animation.view.LaunchableImageView
@@ -539,7 +540,7 @@
)
)
layoutParams.topMargin =
- KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
+ SystemBarUtils.getStatusBarHeight(previewContext) +
resources.getDimensionPixelSize(
com.android.systemui.customization.R.dimen.small_clock_padding_top
)
@@ -620,7 +621,9 @@
}
private suspend fun updateClockAppearance(clock: ClockController) {
- clockController.clock = clock
+ if (!MigrateClocksToBlueprint.isEnabled) {
+ clockController.clock = clock
+ }
val colors = wallpaperColors
if (clockRegistry.seedColor == null && colors != null) {
// Seed color null means users do not override any color on the clock. The default
@@ -638,6 +641,11 @@
if (isWallpaperDark) lightClockColor else darkClockColor
)
}
+ // In clock preview, we should have a seed color for clock
+ // before setting clock to clockEventController to avoid updateColor with seedColor == null
+ if (MigrateClocksToBlueprint.isEnabled) {
+ clockController.clock = clock
+ }
}
private fun onClockChanged() {
if (MigrateClocksToBlueprint.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f6f3bb1..c9251c7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -19,6 +19,7 @@
import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.helper.widget.Layer
+import com.android.internal.policy.SystemBarUtils
import com.android.keyguard.KeyguardClockSwitch.LARGE
import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.customization.R as customizationR
@@ -165,7 +166,7 @@
companion object {
fun getLargeClockTopMargin(context: Context): Int {
- return context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+ return SystemBarUtils.getStatusBarHeight(context) +
context.resources.getDimensionPixelSize(
customizationR.dimen.small_clock_padding_top
) +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
index a90634e..b57e3ec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewSmartspaceViewModel.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
-import android.content.res.Resources
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.SettingsClockSize
@@ -89,14 +88,4 @@
}
}
}
- companion object {
- fun getStatusBarHeight(resource: Resources): Int {
- var result = 0
- val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android")
- if (resourceId > 0) {
- result = resource.getDimensionPixelSize(resourceId)
- }
- return result
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
index 3a19780..a09d58a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt
@@ -21,15 +21,21 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
/**
* Breaks down OCCLUDED->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -43,6 +49,8 @@
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
configurationInteractor: ConfigurationInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
+ keyguardInteractor: KeyguardInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) : DeviceEntryIconTransition {
private val transitionAnimation =
@@ -74,11 +82,28 @@
/** Lockscreen views alpha */
val lockscreenAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- startTime = 233.milliseconds,
- duration = 250.milliseconds,
- onStep = { it },
- name = "OCCLUDED->LOCKSCREEN: lockscreenAlpha",
+ merge(
+ transitionAnimation.sharedFlow(
+ startTime = 233.milliseconds,
+ duration = 250.milliseconds,
+ onStep = { it },
+ name = "OCCLUDED->LOCKSCREEN: lockscreenAlpha",
+ ),
+ // Required to fix a bug where the shade expands while lockscreenAlpha=1f, due to a call
+ // to setOccluded(false) triggering a reset() call in KeyguardViewMediator. The
+ // permanent solution is to only expand the shade once the keyguard transition from
+ // OCCLUDED starts, but that requires more refactoring of expansion amounts. For now,
+ // emit alpha = 0f for OCCLUDED -> LOCKSCREEN whenever isOccluded flips from true to
+ // false while currentState == OCCLUDED, so that alpha = 0f when that expansion occurs.
+ // TODO(b/332946323): Remove this once it's no longer needed.
+ keyguardInteractor.isKeyguardOccluded
+ .pairwise()
+ .filter { (wasOccluded, isOccluded) ->
+ wasOccluded &&
+ !isOccluded &&
+ keyguardTransitionInteractor.getCurrentState() == KeyguardState.OCCLUDED
+ }
+ .map { 0f }
)
val deviceEntryBackgroundViewAlpha: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index 1c11178..5dafd94 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -26,7 +26,9 @@
import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.coroutines.createCoroutineTracingContext
import com.android.app.tracing.coroutines.launch
+import com.android.systemui.Flags.coroutineTracing
import com.android.systemui.util.Assert
+import com.android.systemui.util.Compile
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.Dispatchers
@@ -69,6 +71,12 @@
// presumably want to call view methods that require being called from said UI thread.
val lifecycleCoroutineContext =
Dispatchers.Main + createCoroutineTracingContext() + coroutineContext
+ val traceName =
+ if (Compile.IS_DEBUG && coroutineTracing()) {
+ traceSectionName()
+ } else {
+ DEFAULT_TRACE_NAME
+ }
var lifecycleOwner: ViewLifecycleOwner? = null
val onAttachListener =
object : View.OnAttachStateChangeListener {
@@ -77,6 +85,7 @@
lifecycleOwner?.onDestroy()
lifecycleOwner =
createLifecycleOwnerAndRun(
+ traceName,
view,
lifecycleCoroutineContext,
block,
@@ -93,6 +102,7 @@
if (view.isAttachedToWindow) {
lifecycleOwner =
createLifecycleOwnerAndRun(
+ traceName,
view,
lifecycleCoroutineContext,
block,
@@ -109,18 +119,14 @@
}
private fun createLifecycleOwnerAndRun(
+ nameForTrace: String,
view: View,
coroutineContext: CoroutineContext,
block: suspend LifecycleOwner.(View) -> Unit,
): ViewLifecycleOwner {
return ViewLifecycleOwner(view).apply {
onCreate()
- lifecycleScope.launch(
- "ViewLifecycleOwner(${view::class.java.simpleName})",
- coroutineContext
- ) {
- block(view)
- }
+ lifecycleScope.launch(nameForTrace, coroutineContext) { block(view) }
}
}
@@ -186,3 +192,24 @@
}
}
}
+
+private fun isFrameInteresting(frame: StackWalker.StackFrame): Boolean =
+ frame.className != CURRENT_CLASS_NAME && frame.className != JAVA_ADAPTER_CLASS_NAME
+
+/** Get a name for the trace section include the name of the call site. */
+private fun traceSectionName(): String {
+ val interestingFrame =
+ StackWalker.getInstance().walk { stream ->
+ stream.filter(::isFrameInteresting).limit(5).findFirst()
+ }
+ if (interestingFrame.isPresent) {
+ val frame = interestingFrame.get()
+ return "${frame.className}#${frame.methodName}:${frame.lineNumber} [$DEFAULT_TRACE_NAME]"
+ } else {
+ return DEFAULT_TRACE_NAME
+ }
+}
+
+private const val DEFAULT_TRACE_NAME = "repeatWhenAttached"
+private const val CURRENT_CLASS_NAME = "com.android.systemui.lifecycle.RepeatWhenAttachedKt"
+private const val JAVA_ADAPTER_CLASS_NAME = "com.android.systemui.util.kotlin.JavaAdapterKt"
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index d247122..f08bc17 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -25,8 +25,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
-import com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BasicAppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BasicPackageManagerAppIconLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -102,7 +102,7 @@
@Binds
@MediaProjectionAppSelectorScope
- fun bindAppIconLoader(impl: IconLoaderLibAppIconLoader): AppIconLoader
+ fun bindAppIconLoader(impl: BasicPackageManagerAppIconLoader): BasicAppIconLoader
@Binds
@IntoSet
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt
new file mode 100644
index 0000000..ca5b5f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BadgedAppIconLoader.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.appselector.data
+
+import android.content.ComponentName
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import com.android.launcher3.icons.BaseIconFactory
+import com.android.launcher3.icons.IconFactory
+import com.android.launcher3.util.UserIconInfo
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+class BadgedAppIconLoader
+@Inject
+constructor(
+ private val basicAppIconLoader: BasicAppIconLoader,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val context: Context,
+ private val iconFactoryProvider: Provider<IconFactory>,
+) {
+
+ suspend fun loadIcon(
+ userId: Int,
+ userType: RecentTask.UserType,
+ componentName: ComponentName
+ ): Drawable? =
+ withContext(backgroundDispatcher) {
+ iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory ->
+ val icon =
+ basicAppIconLoader.loadIcon(userId, componentName) ?: return@withContext null
+ val userHandler = UserHandle.of(userId)
+ val iconType = getIconType(userType)
+ val options =
+ BaseIconFactory.IconOptions().apply {
+ setUser(UserIconInfo(userHandler, iconType))
+ }
+ val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
+ badgedIcon.newIcon(context)
+ }
+ }
+
+ private fun getIconType(userType: RecentTask.UserType): Int =
+ when (userType) {
+ RecentTask.UserType.CLONED -> UserIconInfo.TYPE_CLONED
+ RecentTask.UserType.WORK -> UserIconInfo.TYPE_WORK
+ RecentTask.UserType.PRIVATE -> UserIconInfo.TYPE_PRIVATE
+ RecentTask.UserType.STANDARD -> UserIconInfo.TYPE_MAIN
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt
similarity index 61%
rename from packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
rename to packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt
index b85d628..03f6f01 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/BasicAppIconLoader.kt
@@ -17,45 +17,29 @@
package com.android.systemui.mediaprojection.appselector.data
import android.content.ComponentName
-import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import com.android.launcher3.icons.BaseIconFactory.IconOptions
-import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shared.system.PackageManagerWrapper
import javax.inject.Inject
-import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
-interface AppIconLoader {
+interface BasicAppIconLoader {
suspend fun loadIcon(userId: Int, component: ComponentName): Drawable?
}
-class IconLoaderLibAppIconLoader
+class BasicPackageManagerAppIconLoader
@Inject
constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
- private val context: Context,
// Use wrapper to access hidden API that allows to get ActivityInfo for any user id
private val packageManagerWrapper: PackageManagerWrapper,
private val packageManager: PackageManager,
- private val iconFactoryProvider: Provider<IconFactory>
-) : AppIconLoader {
+) : BasicAppIconLoader {
override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
withContext(backgroundDispatcher) {
- iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory ->
- val activityInfo =
- packageManagerWrapper.getActivityInfo(component, userId)
- ?: return@withContext null
- val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null
- val userHandler = UserHandle.of(userId)
- val options = IconOptions().apply { setUser(userHandler) }
- val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
- badgedIcon.newIcon(context)
- }
+ packageManagerWrapper.getActivityInfo(component, userId)?.loadIcon(packageManager)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index e9b4582..3e9b546 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -28,4 +28,12 @@
val baseIntentComponent: ComponentName?,
@ColorInt val colorBackground: Int?,
val isForegroundTask: Boolean,
-)
+ val userType: UserType,
+) {
+ enum class UserType {
+ STANDARD,
+ WORK,
+ PRIVATE,
+ CLONED
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 5dde14b..a6049c8 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -17,6 +17,8 @@
package com.android.systemui.mediaprojection.appselector.data
import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
+import android.content.pm.UserInfo
+import android.os.UserManager
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
@@ -41,7 +43,8 @@
@Background private val coroutineDispatcher: CoroutineDispatcher,
@Background private val backgroundExecutor: Executor,
private val recentTasks: Optional<RecentTasks>,
- private val userTracker: UserTracker
+ private val userTracker: UserTracker,
+ private val userManager: UserManager,
) : RecentTaskListProvider {
private val recents by lazy { recentTasks.getOrNull() }
@@ -65,7 +68,8 @@
it.topActivity,
it.baseIntent?.component,
it.taskDescription?.backgroundColor,
- isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible
+ isForegroundTask = it.taskId in foregroundTaskIds && it.isVisible,
+ userType = userManager.getUserInfo(it.userId).toUserType(),
)
}
}
@@ -81,4 +85,15 @@
continuation.resume(tasks)
}
}
+
+ private fun UserInfo.toUserType(): RecentTask.UserType =
+ if (isCloneProfile) {
+ RecentTask.UserType.CLONED
+ } else if (isManagedProfile) {
+ RecentTask.UserType.WORK
+ } else if (isPrivateProfile) {
+ RecentTask.UserType.PRIVATE
+ } else {
+ RecentTask.UserType.STANDARD
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
index 3fe040a..3b84d2c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTaskViewHolder.kt
@@ -21,12 +21,12 @@
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
-import com.android.systemui.res.R
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelector
-import com.android.systemui.mediaprojection.appselector.data.AppIconLoader
+import com.android.systemui.mediaprojection.appselector.data.BadgedAppIconLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.data.RecentTaskLabelLoader
import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -39,7 +39,7 @@
@AssistedInject
constructor(
@Assisted private val root: ViewGroup,
- private val iconLoader: AppIconLoader,
+ private val iconLoader: BadgedAppIconLoader,
private val thumbnailLoader: RecentTaskThumbnailLoader,
private val labelLoader: RecentTaskLabelLoader,
private val taskViewSizeProvider: TaskPreviewSizeProvider,
@@ -63,7 +63,7 @@
scope.launch {
task.baseIntentComponent?.let { component ->
launch {
- val icon = iconLoader.loadIcon(task.userId, component)
+ val icon = iconLoader.loadIcon(task.userId, task.userType, component)
iconView.setImageDrawable(icon)
}
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 3f8834a..9380d44 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -281,5 +281,10 @@
powerButtonLaunchGestureTriggeredDuringSleep = false,
)
}
+
+ /** Helper method for tests to simulate the device screen state change event. */
+ fun PowerInteractor.setScreenPowerState(screenPowerState: ScreenPowerState) {
+ this.onScreenPowerStateUpdated(screenPowerState)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
index 3b3844a..e424975 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
@@ -31,7 +31,7 @@
import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.util.LifecycleFragment;
import java.util.function.Consumer;
@@ -182,7 +182,7 @@
}
public void setBrightnessMirrorController(
- BrightnessMirrorController brightnessMirrorController) {
+ MirrorController brightnessMirrorController) {
if (mQsImpl != null) {
mQsImpl.setBrightnessMirrorController(brightnessMirrorController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index a000d63..a0607e9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -61,6 +61,7 @@
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
@@ -68,7 +69,6 @@
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.util.Utils;
@@ -544,7 +544,7 @@
}
public void setBrightnessMirrorController(
- BrightnessMirrorController brightnessMirrorController) {
+ @Nullable MirrorController brightnessMirrorController) {
mQSPanelController.setBrightnessMirror(brightnessMirrorController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index cd65119..55dc485 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -24,6 +24,8 @@
import android.view.MotionEvent;
import android.view.View;
+import androidx.annotation.Nullable;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
@@ -38,9 +40,9 @@
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.tuner.TunerService;
@@ -139,6 +141,7 @@
mBrightnessMirrorHandler.onQsPanelAttached();
PagedTileLayout pagedTileLayout= ((PagedTileLayout) mView.getOrCreateTileLayout());
pagedTileLayout.setOnTouchListener(mTileLayoutTouchListener);
+ maybeReinflateBrightnessSlider();
}
@Override
@@ -157,15 +160,18 @@
@Override
protected void onConfigurationChanged() {
mView.updateResources();
+ maybeReinflateBrightnessSlider();
+ if (mView.isListening()) {
+ refreshAllTiles();
+ }
+ }
+
+ private void maybeReinflateBrightnessSlider() {
int newDensity = mView.getResources().getConfiguration().densityDpi;
if (newDensity != mLastDensity) {
mLastDensity = newDensity;
reinflateBrightnessSlider();
}
-
- if (mView.isListening()) {
- refreshAllTiles();
- }
}
private void reinflateBrightnessSlider() {
@@ -210,7 +216,7 @@
}
}
- public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) {
+ public void setBrightnessMirror(@Nullable MirrorController brightnessMirrorController) {
mBrightnessMirrorHandler.setController(brightnessMirrorController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index b0707db..6eae32a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -39,6 +39,7 @@
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
@@ -51,7 +52,6 @@
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.BluetoothController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 2f8fe42..3eeb2a3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -25,6 +25,7 @@
import android.provider.Settings;
import android.safetycenter.SafetyCenterManager;
import android.service.quicksettings.Tile;
+import android.text.TextUtils;
import android.view.View;
import android.widget.Switch;
@@ -127,7 +128,7 @@
} else {
state.secondaryLabel = mContext.getString(R.string.quick_settings_camera_mic_available);
}
- state.contentDescription = state.label;
+ state.contentDescription = TextUtils.concat(state.label, ", ", state.secondaryLabel);
state.expandedAccessibilityClassName = Switch.class.getName();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 6710504..3d86e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -34,6 +34,7 @@
import com.android.systemui.qs.QSImpl
import com.android.systemui.qs.dagger.QSSceneComponent
import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.MirrorController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.util.kotlin.sample
@@ -68,6 +69,9 @@
*/
val qsView: Flow<View>
+ /** Sets the [MirrorController] in [QSImpl]. Set to `null` to remove. */
+ fun setBrightnessMirrorController(mirrorController: MirrorController?)
+
/**
* Inflate an instance of [QSImpl] for this context. Once inflated, it will be available in
* [qsView]. Re-inflations due to configuration changes will use the last used [context].
@@ -93,6 +97,9 @@
val isQsFullyCollapsed: Boolean
get() = true
+ /** Request that the customizer be closed. Possibly animating it. */
+ fun requestCloseCustomizer()
+
sealed interface State {
val isVisible: Boolean
@@ -203,7 +210,7 @@
applicationScope.launch {
launch {
state.sample(_isCustomizing, ::Pair).collect { (state, customizing) ->
- _qsImpl.value?.apply {
+ qsImpl.value?.apply {
if (state != QSSceneAdapter.State.QS && customizing) {
this@apply.closeCustomizerImmediately()
}
@@ -277,6 +284,14 @@
bottomNavBarSize.emit(padding)
}
+ override fun requestCloseCustomizer() {
+ qsImpl.value?.closeCustomizer()
+ }
+
+ override fun setBrightnessMirrorController(mirrorController: MirrorController?) {
+ qsImpl.value?.setBrightnessMirrorController(mirrorController)
+ }
+
private fun QSImpl.applyState(state: QSSceneAdapter.State) {
setQsVisible(state.isVisible)
setExpanded(state.isVisible && state.expansion > 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index c695d4c..4c86471 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -20,13 +20,13 @@
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import java.util.concurrent.atomic.AtomicBoolean
@@ -38,6 +38,7 @@
class QuickSettingsSceneViewModel
@Inject
constructor(
+ val brightnessMirrorViewModel: BrightnessMirrorViewModel,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val qsSceneAdapter: QSSceneAdapter,
val notifications: NotificationsPlaceholderViewModel,
@@ -47,7 +48,9 @@
val destinationScenes =
qsSceneAdapter.isCustomizing.map { customizing ->
if (customizing) {
- mapOf<UserAction, UserActionResult>(Back to UserActionResult(Scenes.QuickSettings))
+ // TODO(b/332749288) Empty map so there are no back handlers and back can close
+ // customizer
+ emptyMap()
// TODO(b/330200163) Add an Up from Bottom to be able to collapse the shade
// while customizing
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index f621f11..4ece7b6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -99,6 +99,7 @@
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeViewController;
@@ -239,6 +240,11 @@
} else {
mShadeViewControllerLazy.get().finishInputFocusTransfer(velocity);
}
+ } else if (action == ACTION_UP) {
+ // Gesture was too short to be picked up by scene container touch
+ // handling; programmatically start the transition to shade scene.
+ mSceneInteractor.get().changeScene(
+ Scenes.Shade, "short launcher swipe");
}
}
event.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 5e4919d..4d34a86 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -16,6 +16,7 @@
package com.android.systemui.recordissue
+import android.app.IActivityManager
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
@@ -51,7 +52,7 @@
@Inject
constructor(
controller: RecordingController,
- @LongRunning executor: Executor,
+ @LongRunning private val bgExecutor: Executor,
@Main handler: Handler,
uiEventLogger: UiEventLogger,
notificationManager: NotificationManager,
@@ -60,10 +61,12 @@
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val panelInteractor: PanelInteractor,
private val issueRecordingState: IssueRecordingState,
+ private val iActivityManager: IActivityManager,
+ private val launcherApps: LauncherApps,
) :
RecordingService(
controller,
- executor,
+ bgExecutor,
handler,
uiEventLogger,
notificationManager,
@@ -103,12 +106,26 @@
// ViewCapture needs to save it's data before it is disabled, or else the data will
// be lost. This is expected to change in the near future, and when that happens
// this line should be removed.
- getSystemService(LauncherApps::class.java)?.saveViewCaptureData()
+ launcherApps.saveViewCaptureData()
TraceUtils.traceStop(contentResolver)
issueRecordingState.isRecording = false
}
ACTION_SHARE -> {
- shareRecording(intent)
+ bgExecutor.execute {
+ mNotificationManager.cancelAsUser(
+ null,
+ mNotificationId,
+ UserHandle(mUserContextTracker.userContext.userId)
+ )
+
+ val screenRecording = intent.getParcelableExtra(EXTRA_PATH, Uri::class.java)
+ if (issueRecordingState.takeBugReport) {
+ iActivityManager.requestBugReportWithExtraAttachment(screenRecording)
+ } else {
+ shareRecording(screenRecording)
+ }
+ }
+
dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
panelInteractor.collapsePanels()
@@ -122,23 +139,17 @@
return super.onStartCommand(intent, flags, startId)
}
- private fun shareRecording(intent: Intent) {
+ private fun shareRecording(screenRecording: Uri?) {
val sharableUri: Uri =
zipAndPackageRecordings(
TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get(),
- intent.getStringExtra(EXTRA_PATH)
+ screenRecording
)
?: return
val sendIntent =
FileSender.buildSendIntent(this, listOf(sharableUri))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- mNotificationManager.cancelAsUser(
- null,
- mNotificationId,
- UserHandle(mUserContextTracker.userContext.userId)
- )
-
// TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
mKeyguardDismissUtil.executeWhenUnlocked(
{
@@ -150,7 +161,7 @@
)
}
- private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecordingUri: String?): Uri? {
+ private fun zipAndPackageRecordings(traceFiles: List<File>, screenRecording: Uri?): Uri? {
try {
externalCacheDir?.mkdirs()
val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir)
@@ -160,8 +171,8 @@
Files.copy(file.toPath(), os)
os.closeEntry()
}
- if (screenRecordingUri != null) {
- contentResolver.openInputStream(Uri.parse(screenRecordingUri))?.use {
+ if (screenRecording != null) {
+ contentResolver.openInputStream(screenRecording)?.use {
os.putNextEntry(ZipEntry(SCREEN_RECORDING_ZIP_LABEL))
it.transferTo(os)
os.closeEntry()
@@ -215,7 +226,7 @@
fun getStartIntent(
context: Context,
screenRecord: Boolean,
- winscopeTracing: Boolean
+ winscopeTracing: Boolean,
): Intent =
Intent(context, IssueRecordingService::class.java)
.setAction(ACTION_START)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
index 394c5c2..12ed06d 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
@@ -25,6 +25,8 @@
private val listeners = CopyOnWriteArrayList<Runnable>()
+ var takeBugReport: Boolean = false
+
var isRecording = false
set(value) {
field = value
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index dab61fa..68b8836 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -63,6 +63,7 @@
private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
private val userFileManager: UserFileManager,
private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
+ private val issueRecordingState: IssueRecordingState,
@Assisted private val onStarted: Consumer<IssueRecordingConfig>,
) : SystemUIDialog.Delegate {
@@ -74,6 +75,7 @@
}
@SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
+ @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var bugReportSwitch: Switch
private lateinit var issueTypeButton: Button
@MainThread
@@ -86,6 +88,7 @@
setPositiveButton(
R.string.qs_record_issue_start,
{ _, _ ->
+ issueRecordingState.takeBugReport = bugReportSwitch.isChecked
onStarted.accept(
IssueRecordingConfig(
screenRecordSwitch.isChecked,
@@ -113,6 +116,7 @@
bgExecutor.execute { onScreenRecordSwitchClicked() }
}
}
+ bugReportSwitch = requireViewById(R.id.bugreport_switch)
val startButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
issueTypeButton = requireViewById(R.id.issue_type_button)
issueTypeButton.setOnClickListener {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index 994b012..3082eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -24,6 +24,8 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSource
+import com.android.systemui.util.kotlin.WithPrev
+import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -34,6 +36,7 @@
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Source of truth for scene framework application state. */
@@ -44,7 +47,32 @@
private val config: SceneContainerConfig,
private val dataSource: SceneDataSource,
) {
- val currentScene: StateFlow<SceneKey> = dataSource.currentScene
+ private val previousAndCurrentScene: StateFlow<WithPrev<SceneKey?, SceneKey>> =
+ dataSource.currentScene
+ .pairwise()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = WithPrev(null, dataSource.currentScene.value),
+ )
+
+ val currentScene: StateFlow<SceneKey> =
+ previousAndCurrentScene
+ .map { it.newValue }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = previousAndCurrentScene.value.newValue,
+ )
+
+ val previousScene: StateFlow<SceneKey?> =
+ previousAndCurrentScene
+ .map { it.previousValue }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = previousAndCurrentScene.value.previousValue,
+ )
private val _isVisible = MutableStateFlow(true)
val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 2ccd3b9..0239455 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -140,6 +140,14 @@
)
/**
+ * The previous scene.
+ *
+ * This is effectively the previous value of [currentScene] which means that all caveats, for
+ * example regarding when in a transition the current scene changes, apply.
+ */
+ val previousScene: StateFlow<SceneKey?> = repository.previousScene
+
+ /**
* Returns the keys of all scenes in the container.
*
* The scenes will be sorted in z-order such that the last one is the one that should be
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 5664d59..c929196 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
+import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
import dagger.Module
import dagger.Provides
@@ -41,11 +42,12 @@
inline val isEnabled
get() =
sceneContainer() && // mainAconfigFlag
- KeyguardBottomAreaRefactor.isEnabled &&
- MigrateClocksToBlueprint.isEnabled &&
- ComposeLockscreen.isEnabled &&
- MediaInSceneContainerFlag.isEnabled &&
+ ComposeLockscreen.isEnabled &&
+ KeyguardBottomAreaRefactor.isEnabled &&
KeyguardWmStateRefactor.isEnabled &&
+ MediaInSceneContainerFlag.isEnabled &&
+ MigrateClocksToBlueprint.isEnabled &&
+ NotificationsHeadsUpRefactor.isEnabled &&
PredictiveBackSysUiFlag.isEnabled
// NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
@@ -55,11 +57,13 @@
/** The set of secondary flags which must be enabled for scene container to work properly */
inline fun getSecondaryFlags(): Sequence<FlagToken> =
sequenceOf(
- KeyguardBottomAreaRefactor.token,
- MigrateClocksToBlueprint.token,
- KeyguardWmStateRefactor.token,
ComposeLockscreen.token,
+ KeyguardBottomAreaRefactor.token,
+ KeyguardWmStateRefactor.token,
MediaInSceneContainerFlag.token,
+ MigrateClocksToBlueprint.token,
+ NotificationsHeadsUpRefactor.token,
+ PredictiveBackSysUiFlag.token,
// NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b2c01e1..cbb61b3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -206,7 +206,7 @@
break;
case ACTION_SHARE:
- Uri shareUri = Uri.parse(intent.getStringExtra(EXTRA_PATH));
+ Uri shareUri = intent.getParcelableExtra(EXTRA_PATH, Uri.class);
Intent shareIntent = new Intent(Intent.ACTION_SEND)
.setType("video/mp4")
@@ -356,7 +356,7 @@
PendingIntent.getService(
this,
REQUEST_CODE,
- getShareIntent(this, uri != null ? uri.toString() : null),
+ getShareIntent(this, uri),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.build();
@@ -512,7 +512,7 @@
return new Intent(context, this.getClass()).setAction(ACTION_STOP_NOTIF);
}
- private Intent getShareIntent(Context context, String path) {
+ private Intent getShareIntent(Context context, Uri path) {
return new Intent(context, this.getClass()).setAction(ACTION_SHARE)
.putExtra(EXTRA_PATH, path);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
new file mode 100644
index 0000000..caa67df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.ActivityOptions
+import android.app.BroadcastOptions
+import android.app.ExitTransitionCoordinator
+import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.UserHandle
+import android.util.Log
+import android.util.Pair
+import android.view.View
+import android.view.Window
+import com.android.app.tracing.coroutines.launch
+import com.android.internal.app.ChooserActivity
+import com.android.systemui.dagger.qualifiers.Application
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+
+class ActionExecutor
+@AssistedInject
+constructor(
+ private val intentExecutor: ActionIntentExecutor,
+ @Application private val applicationScope: CoroutineScope,
+ @Assisted val window: Window,
+ @Assisted val transitionView: View,
+ @Assisted val onDismiss: (() -> Unit)
+) {
+
+ var isPendingSharedTransition = false
+ private set
+
+ fun startSharedTransition(intent: Intent, user: UserHandle, overrideTransition: Boolean) {
+ isPendingSharedTransition = true
+ val windowTransition = createWindowTransition()
+ applicationScope.launch("$TAG#launchIntentAsync") {
+ intentExecutor.launchIntent(
+ intent,
+ user,
+ overrideTransition,
+ windowTransition.first,
+ windowTransition.second
+ )
+ }
+ }
+
+ fun sendPendingIntent(pendingIntent: PendingIntent) {
+ try {
+ val options = BroadcastOptions.makeBasic()
+ options.setInteractive(true)
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ pendingIntent.send(options.toBundle())
+ onDismiss.invoke()
+ } catch (e: PendingIntent.CanceledException) {
+ Log.e(TAG, "Intent cancelled", e)
+ }
+ }
+
+ /**
+ * Supplies the necessary bits for the shared element transition to share sheet. Note that once
+ * called, the action intent to share must be sent immediately after.
+ */
+ private fun createWindowTransition(): Pair<ActivityOptions, ExitTransitionCoordinator> {
+ val callbacks: ExitTransitionCallbacks =
+ object : ExitTransitionCallbacks {
+ override fun isReturnTransitionAllowed(): Boolean {
+ return false
+ }
+
+ override fun hideSharedElements() {
+ isPendingSharedTransition = false
+ onDismiss.invoke()
+ }
+
+ override fun onFinish() {}
+ }
+ return ActivityOptions.startSharedElementAnimation(
+ window,
+ callbacks,
+ null,
+ Pair.create(transitionView, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)
+ )
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(window: Window, transitionView: View, onDismiss: (() -> Unit)): ActionExecutor
+ }
+
+ companion object {
+ private const val TAG = "ActionExecutor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 8e9769ab..a0cef52 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -23,7 +23,9 @@
import android.content.Context
import android.content.Intent
import android.net.Uri
+import android.os.UserHandle
import com.android.systemui.res.R
+import com.android.systemui.screenshot.scroll.LongScreenshotActivity
object ActionIntentCreator {
/** @return a chooser intent to share the given URI. */
@@ -89,6 +91,14 @@
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
+ /** @return an Intent to start the LongScreenshotActivity */
+ fun createLongScreenshotIntent(owner: UserHandle, context: Context): Intent {
+ return Intent(context, LongScreenshotActivity::class.java)
+ .putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE, owner)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ }
+
private const val EXTRA_EDIT_SOURCE = "edit_source"
private const val EDIT_SOURCE_SCREENSHOT = "screenshot"
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 1f9853b..4eca51d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -25,7 +25,6 @@
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
-import android.util.Pair
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter
@@ -67,20 +66,22 @@
*/
fun launchIntentAsync(
intent: Intent,
- transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
user: UserHandle,
overrideTransition: Boolean,
+ options: ActivityOptions?,
+ transitionCoordinator: ExitTransitionCoordinator?,
) {
applicationScope.launch("$TAG#launchIntentAsync") {
- launchIntent(intent, transition, user, overrideTransition)
+ launchIntent(intent, user, overrideTransition, options, transitionCoordinator)
}
}
suspend fun launchIntent(
intent: Intent,
- transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
user: UserHandle,
overrideTransition: Boolean,
+ options: ActivityOptions?,
+ transitionCoordinator: ExitTransitionCoordinator?,
) {
if (screenshotActionDismissSystemWindows()) {
keyguardController.dismiss()
@@ -90,14 +91,12 @@
} else {
dismissKeyguard()
}
- transition?.second?.startExit()
+ transitionCoordinator?.startExit()
if (user == myUserHandle()) {
- withContext(mainDispatcher) {
- context.startActivity(intent, transition?.first?.toBundle())
- }
+ withContext(mainDispatcher) { context.startActivity(intent, options?.toBundle()) }
} else {
- launchCrossProfileIntent(user, intent, transition?.first?.toBundle())
+ launchCrossProfileIntent(user, intent, options?.toBundle())
}
if (overrideTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index ca0a539..0ccb19c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -16,54 +16,40 @@
package com.android.systemui.screenshot
-import android.app.ActivityOptions
-import android.app.BroadcastOptions
-import android.app.ExitTransitionCoordinator
-import android.app.PendingIntent
+import android.app.assist.AssistContent
import android.content.Context
-import android.content.Intent
-import android.os.Process
-import android.os.UserHandle
-import android.provider.DeviceConfig
import android.util.Log
-import android.util.Pair
import androidx.appcompat.content.res.AppCompatResources
-import com.android.app.tracing.coroutines.launch
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
import com.android.systemui.screenshot.ActionIntentCreator.createEdit
import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubject
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED
-import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
+import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import java.text.DateFormat
-import java.util.Date
-import kotlinx.coroutines.CoroutineScope
/**
* Provides actions for screenshots. This class can be overridden by a vendor-specific SysUI
* implementation.
*/
interface ScreenshotActionsProvider {
- fun setCompletedScreenshot(result: SavedImageData)
- fun isPendingSharedTransition(): Boolean
+ fun onScrollChipReady(onClick: Runnable)
+ fun setCompletedScreenshot(result: ScreenshotSavedResult)
+
+ fun onAssistContentAvailable(assistContent: AssistContent) {}
interface Factory {
fun create(
request: ScreenshotData,
requestId: String,
- windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
- requestDismissal: () -> Unit,
+ actionExecutor: ActionExecutor,
): ScreenshotActionsProvider
}
}
@@ -73,182 +59,145 @@
constructor(
private val context: Context,
private val viewModel: ScreenshotViewModel,
- private val actionExecutor: ActionIntentExecutor,
private val smartActionsProvider: SmartActionsProvider,
private val uiEventLogger: UiEventLogger,
- @Application private val applicationScope: CoroutineScope,
@Assisted val request: ScreenshotData,
@Assisted val requestId: String,
- @Assisted val windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
- @Assisted val requestDismissal: () -> Unit,
+ @Assisted val actionExecutor: ActionExecutor,
) : ScreenshotActionsProvider {
- private var pendingAction: ((SavedImageData) -> Unit)? = null
- private var result: SavedImageData? = null
- private var isPendingSharedTransition = false
+ private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null
+ private var result: ScreenshotSavedResult? = null
init {
viewModel.setPreviewAction {
debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" }
uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString)
onDeferrableActionTapped { result ->
- startSharedTransition(createEdit(result.uri, context), true)
+ actionExecutor.startSharedTransition(
+ createEdit(result.uri, context),
+ result.user,
+ true
+ )
}
}
viewModel.addAction(
- ActionButtonViewModel(
+ ActionButtonAppearance(
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit),
context.resources.getString(R.string.screenshot_edit_label),
context.resources.getString(R.string.screenshot_edit_description),
- ) {
- debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
- uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
- onDeferrableActionTapped { result ->
- startSharedTransition(createEdit(result.uri, context), true)
- }
+ )
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
+ uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createEdit(result.uri, context),
+ result.user,
+ true
+ )
}
- )
+ }
+
viewModel.addAction(
- ActionButtonViewModel(
+ ActionButtonAppearance(
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share),
context.resources.getString(R.string.screenshot_share_label),
context.resources.getString(R.string.screenshot_share_description),
- ) {
- debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
- uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
- onDeferrableActionTapped { result ->
- startSharedTransition(createShareWithSubject(result.uri, result.subject), false)
- }
+ )
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
+ uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createShareWithSubject(result.uri, result.subject),
+ result.user,
+ false
+ )
}
- )
- if (smartActionsEnabled(request.userHandle ?: Process.myUserHandle())) {
- smartActionsProvider.requestQuickShare(request, requestId) { quickShare ->
- if (!quickShare.actionIntent.isImmutable) {
- viewModel.addAction(
- ActionButtonViewModel(
- quickShare.getIcon().loadDrawable(context),
- quickShare.title,
- quickShare.title
- ) {
- debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" }
- onDeferrableActionTapped { result ->
- uiEventLogger.log(
- SCREENSHOT_SMART_ACTION_TAPPED,
- 0,
- request.packageNameString
- )
- sendPendingIntent(
- smartActionsProvider
- .wrapIntent(
- quickShare,
- result.uri,
- result.subject,
- requestId
- )
- .actionIntent
- )
- }
- }
+ }
+
+ smartActionsProvider.requestQuickShare(request, requestId) { quickShare ->
+ if (!quickShare.actionIntent.isImmutable) {
+ viewModel.addAction(
+ ActionButtonAppearance(
+ quickShare.getIcon().loadDrawable(context),
+ quickShare.title,
+ quickShare.title
)
- } else {
- Log.w(TAG, "Received immutable quick share pending intent; ignoring")
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" }
+ onDeferrableActionTapped { result ->
+ uiEventLogger.log(
+ SCREENSHOT_SMART_ACTION_TAPPED,
+ 0,
+ request.packageNameString
+ )
+ val pendingIntentWithUri =
+ smartActionsProvider.wrapIntent(
+ quickShare,
+ result.uri,
+ result.subject,
+ requestId
+ )
+ actionExecutor.sendPendingIntent(pendingIntentWithUri)
+ }
}
+ } else {
+ Log.w(TAG, "Received immutable quick share pending intent; ignoring")
}
}
}
- override fun setCompletedScreenshot(result: SavedImageData) {
+ override fun onScrollChipReady(onClick: Runnable) {
+ viewModel.addAction(
+ ActionButtonAppearance(
+ AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll),
+ context.resources.getString(R.string.screenshot_scroll_label),
+ context.resources.getString(R.string.screenshot_scroll_label),
+ )
+ ) {
+ onClick.run()
+ }
+ }
+
+ override fun setCompletedScreenshot(result: ScreenshotSavedResult) {
if (this.result != null) {
Log.e(TAG, "Got a second completed screenshot for existing request!")
return
}
- if (result.uri == null || result.owner == null || result.imageTime == null) {
- Log.e(TAG, "Invalid result provided!")
- return
- }
- if (result.subject == null) {
- result.subject = getSubjectString(result.imageTime)
- }
this.result = result
pendingAction?.invoke(result)
- if (smartActionsEnabled(result.owner)) {
- smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions ->
- viewModel.addActions(
- smartActions.map {
- ActionButtonViewModel(
- it.getIcon().loadDrawable(context),
- it.title,
- it.title
- ) {
- sendPendingIntent(it.actionIntent)
- }
+ smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions ->
+ smartActions.forEach {
+ smartActions.forEach { action ->
+ viewModel.addAction(
+ ActionButtonAppearance(
+ action.getIcon().loadDrawable(context),
+ action.title,
+ action.title,
+ )
+ ) {
+ actionExecutor.sendPendingIntent(action.actionIntent)
}
- )
+ }
}
}
}
- override fun isPendingSharedTransition(): Boolean {
- return isPendingSharedTransition
- }
-
- private fun onDeferrableActionTapped(onResult: (SavedImageData) -> Unit) {
+ private fun onDeferrableActionTapped(onResult: (ScreenshotSavedResult) -> Unit) {
result?.let { onResult.invoke(it) } ?: run { pendingAction = onResult }
}
- private fun startSharedTransition(intent: Intent, overrideTransition: Boolean) {
- val user =
- result?.owner
- ?: run {
- Log.wtf(TAG, "User handle not provided in screenshot result! Result: $result")
- return
- }
- isPendingSharedTransition = true
- applicationScope.launch("$TAG#launchIntentAsync") {
- actionExecutor.launchIntent(intent, windowTransition.invoke(), user, overrideTransition)
- }
- }
-
- private fun sendPendingIntent(pendingIntent: PendingIntent) {
- try {
- val options = BroadcastOptions.makeBasic()
- options.setInteractive(true)
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- )
- pendingIntent.send(options.toBundle())
- requestDismissal.invoke()
- } catch (e: PendingIntent.CanceledException) {
- Log.e(TAG, "Intent cancelled", e)
- }
- }
-
- private fun smartActionsEnabled(user: UserHandle): Boolean {
- val savingToOtherUser = user != Process.myUserHandle()
- return !savingToOtherUser &&
- DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
- true
- )
- }
-
- private fun getSubjectString(imageTime: Long): String {
- val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime))
- return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate)
- }
-
@AssistedFactory
interface Factory : ScreenshotActionsProvider.Factory {
override fun create(
request: ScreenshotData,
requestId: String,
- windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>,
- requestDismissal: () -> Unit,
+ actionExecutor: ActionExecutor,
): DefaultScreenshotActionsProvider
}
companion object {
private const val TAG = "ScreenshotActionsProvider"
- private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 70d1129..13dd229 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -34,7 +34,6 @@
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ExitTransitionCoordinator;
import android.app.ICompatCameraControlCallback;
@@ -51,7 +50,6 @@
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Process;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -59,17 +57,12 @@
import android.util.Log;
import android.util.Pair;
import android.view.Display;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
import android.widget.Toast;
import android.window.WindowContext;
@@ -84,10 +77,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
-import com.android.systemui.screenshot.scroll.LongScreenshotActivity;
-import com.android.systemui.screenshot.scroll.LongScreenshotData;
-import com.android.systemui.screenshot.scroll.ScrollCaptureClient;
-import com.android.systemui.screenshot.scroll.ScrollCaptureController;
+import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor;
import com.android.systemui.util.Assert;
import com.google.common.util.concurrent.ListenableFuture;
@@ -100,12 +90,9 @@
import java.util.List;
import java.util.UUID;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
import java.util.function.Consumer;
import javax.inject.Provider;
@@ -117,34 +104,6 @@
public class ScreenshotController {
private static final String TAG = logTag(ScreenshotController.class);
- private ScrollCaptureResponse mLastScrollCaptureResponse;
- private ListenableFuture<ScrollCaptureResponse> mLastScrollCaptureRequest;
-
- /**
- * This is effectively a no-op, but we need something non-null to pass in, in order to
- * successfully override the pending activity entrance animation.
- */
- static final IRemoteAnimationRunner.Stub SCREENSHOT_REMOTE_RUNNER =
- new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(
- @WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Log.e(TAG, "Error finishing screenshot remote animation", e);
- }
- }
-
- @Override
- public void onAnimationCancelled() {
- }
- };
-
/**
* POD used in the AsyncTask which saves an image in the background.
*/
@@ -242,22 +201,20 @@
private final ExecutorService mBgExecutor;
private final BroadcastSender mBroadcastSender;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ActionExecutor mActionExecutor;
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mWindowLayoutParams;
@Nullable
private final ScreenshotSoundController mScreenshotSoundController;
- private final ScrollCaptureClient mScrollCaptureClient;
private final PhoneWindow mWindow;
private final DisplayManager mDisplayManager;
private final int mDisplayId;
- private final ScrollCaptureController mScrollCaptureController;
- private final LongScreenshotData mLongScreenshotHolder;
- private final boolean mIsLowRamDevice;
+ private final ScrollCaptureExecutor mScrollCaptureExecutor;
private final ScreenshotNotificationSmartActionsProvider
mScreenshotNotificationSmartActionsProvider;
private final TimeoutHandler mScreenshotHandler;
- private final ActionIntentExecutor mActionExecutor;
+ private final ActionIntentExecutor mActionIntentExecutor;
private final UserManager mUserManager;
private final AssistContentRequester mAssistContentRequester;
@@ -299,19 +256,17 @@
ScreenshotActionsProvider.Factory actionsProviderFactory,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
- ScrollCaptureClient scrollCaptureClient,
UiEventLogger uiEventLogger,
ImageExporter imageExporter,
ImageCapture imageCapture,
@Main Executor mainExecutor,
- ScrollCaptureController scrollCaptureController,
- LongScreenshotData longScreenshotHolder,
- ActivityManager activityManager,
+ ScrollCaptureExecutor scrollCaptureExecutor,
TimeoutHandler timeoutHandler,
BroadcastSender broadcastSender,
BroadcastDispatcher broadcastDispatcher,
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
- ActionIntentExecutor actionExecutor,
+ ActionIntentExecutor actionIntentExecutor,
+ ActionExecutor.Factory actionExecutorFactory,
UserManager userManager,
AssistContentRequester assistContentRequester,
MessageContainerController messageContainerController,
@@ -322,14 +277,11 @@
mScreenshotSmartActions = screenshotSmartActions;
mActionsProviderFactory = actionsProviderFactory;
mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
- mScrollCaptureClient = scrollCaptureClient;
mUiEventLogger = uiEventLogger;
mImageExporter = imageExporter;
mImageCapture = imageCapture;
mMainExecutor = mainExecutor;
- mScrollCaptureController = scrollCaptureController;
- mLongScreenshotHolder = longScreenshotHolder;
- mIsLowRamDevice = activityManager.isLowRamDevice();
+ mScrollCaptureExecutor = scrollCaptureExecutor;
mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider;
mBgExecutor = Executors.newSingleThreadExecutor();
mBroadcastSender = broadcastSender;
@@ -345,7 +297,7 @@
final Context displayContext = context.createDisplayContext(getDisplay());
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mFlags = flags;
- mActionExecutor = actionExecutor;
+ mActionIntentExecutor = actionIntentExecutor;
mUserManager = userManager;
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
@@ -369,6 +321,12 @@
mConfigChanges.applyNewConfig(context.getResources());
reloadAssets();
+ mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy.getScreenshotPreview(),
+ () -> {
+ requestDismissal(null);
+ return Unit.INSTANCE;
+ });
+
// Sound is only reproduced from the controller of the default display.
if (displayId == Display.DEFAULT_DISPLAY) {
mScreenshotSoundController = screenshotSoundController.get();
@@ -395,10 +353,10 @@
Assert.isMainThread();
mCurrentRequestCallback = requestCallback;
- if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) {
+ if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+ && screenshot.getBitmap() == null) {
Rect bounds = getFullScreenRect();
- screenshot.setBitmap(
- mImageCapture.captureDisplay(mDisplayId, bounds));
+ screenshot.setBitmap(mImageCapture.captureDisplay(mDisplayId, bounds));
screenshot.setScreenBounds(bounds);
}
@@ -447,12 +405,16 @@
if (screenshotShelfUi()) {
final UUID requestId = UUID.randomUUID();
final String screenshotId = String.format("Screenshot_%s", requestId);
- mActionsProvider = mActionsProviderFactory.create(screenshot, screenshotId,
- this::createWindowTransition, () -> {
- mViewProxy.requestDismissal(null);
- return Unit.INSTANCE;
- });
+ mActionsProvider = mActionsProviderFactory.create(
+ screenshot, screenshotId, mActionExecutor);
saveScreenshotInBackground(screenshot, requestId, finisher);
+
+ if (screenshot.getTaskId() >= 0) {
+ mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
+ assistContent -> {
+ mActionsProvider.onAssistContentAvailable(assistContent);
+ });
+ }
} else {
saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
@@ -541,7 +503,7 @@
boolean isPendingSharedTransition() {
if (screenshotShelfUi()) {
- return mActionsProvider != null && mActionsProvider.isPendingSharedTransition();
+ return mActionExecutor.isPendingSharedTransition();
} else {
return mViewProxy.isPendingSharedTransition();
}
@@ -592,8 +554,9 @@
@Override
public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) {
- mActionExecutor.launchIntentAsync(
- intent, createWindowTransition(), owner, overrideTransition);
+ Pair<ActivityOptions, ExitTransitionCoordinator> exit = createWindowTransition();
+ mActionIntentExecutor.launchIntentAsync(
+ intent, owner, overrideTransition, exit.first, exit.second);
}
@Override
@@ -630,9 +593,8 @@
mViewProxy.hideScrollChip();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
- mScreenshotHandler.postDelayed(() -> {
- requestScrollCapture(owner);
- }, 150);
+ mScreenshotHandler.postDelayed(
+ () -> requestScrollCapture(owner), 150);
mViewProxy.updateInsets(
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
@@ -655,119 +617,51 @@
}
private void requestScrollCapture(UserHandle owner) {
- if (!allowLongScreenshots()) {
- Log.d(TAG, "Long screenshots not supported on this device");
+ mScrollCaptureExecutor.requestScrollCapture(
+ mDisplayId,
+ mWindow.getDecorView().getWindowToken(),
+ (response) -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
+ 0, response.getPackageName());
+ if (screenshotShelfUi() && mActionsProvider != null) {
+ mActionsProvider.onScrollChipReady(
+ () -> onScrollButtonClicked(owner, response));
+ } else {
+ mViewProxy.showScrollChip(response.getPackageName(),
+ () -> onScrollButtonClicked(owner, response));
+ }
+ return Unit.INSTANCE;
+ }
+ );
+ }
+
+ private void onScrollButtonClicked(UserHandle owner, ScrollCaptureResponse response) {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "scroll chip tapped");
+ }
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
+ response.getPackageName());
+ Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
+ if (newScreenshot == null) {
+ Log.e(TAG, "Failed to capture current screenshot for scroll transition!");
return;
}
- mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());
- if (mLastScrollCaptureRequest != null) {
- mLastScrollCaptureRequest.cancel(true);
- }
- final ListenableFuture<ScrollCaptureResponse> future = mScrollCaptureClient.request(
- mDisplayId);
- mLastScrollCaptureRequest = future;
- mLastScrollCaptureRequest.addListener(() ->
- onScrollCaptureResponseReady(future, owner), mMainExecutor);
+ // delay starting scroll capture to make sure scrim is up before the app moves
+ mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+ mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner));
}
- private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture,
- UserHandle owner) {
- try {
- if (mLastScrollCaptureResponse != null) {
- mLastScrollCaptureResponse.close();
- mLastScrollCaptureResponse = null;
- }
- if (responseFuture.isCancelled()) {
- return;
- }
- mLastScrollCaptureResponse = responseFuture.get();
- if (!mLastScrollCaptureResponse.isConnected()) {
- // No connection means that the target window wasn't found
- // or that it cannot support scroll capture.
- Log.d(TAG, "ScrollCapture: " + mLastScrollCaptureResponse.getDescription() + " ["
- + mLastScrollCaptureResponse.getWindowTitle() + "]");
- return;
- }
- Log.d(TAG, "ScrollCapture: connected to window ["
- + mLastScrollCaptureResponse.getWindowTitle() + "]");
+ private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
+ mScrollCaptureExecutor.executeBatchScrollCapture(response,
+ () -> {
+ final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent(
+ owner, mContext);
+ mActionIntentExecutor.launchIntentAsync(intent, owner, true,
+ ActivityOptions.makeCustomAnimation(mContext, 0, 0), null);
- final ScrollCaptureResponse response = mLastScrollCaptureResponse;
- mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
- Bitmap newScreenshot =
- mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
-
- if (newScreenshot != null) {
- // delay starting scroll capture to make sure scrim is up before the app
- // moves
- mViewProxy.prepareScrollingTransition(
- response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
- () -> runBatchScrollCapture(response, owner));
- } else {
- Log.wtf(TAG, "failed to capture current screenshot for scroll transition");
- }
- });
- } catch (InterruptedException | ExecutionException e) {
- Log.e(TAG, "requestScrollCapture failed", e);
- }
- }
-
- ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
-
- private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
- // Clear the reference to prevent close() in dismissScreenshot
- mLastScrollCaptureResponse = null;
-
- if (mLongScreenshotFuture != null) {
- mLongScreenshotFuture.cancel(true);
- }
- mLongScreenshotFuture = mScrollCaptureController.run(response);
- mLongScreenshotFuture.addListener(() -> {
- ScrollCaptureController.LongScreenshot longScreenshot;
- try {
- longScreenshot = mLongScreenshotFuture.get();
- } catch (CancellationException e) {
- Log.e(TAG, "Long screenshot cancelled");
- return;
- } catch (InterruptedException | ExecutionException e) {
- Log.e(TAG, "Exception", e);
- mViewProxy.restoreNonScrollingUi();
- return;
- }
-
- if (longScreenshot.getHeight() == 0) {
- mViewProxy.restoreNonScrollingUi();
- return;
- }
-
- mLongScreenshotHolder.setLongScreenshot(longScreenshot);
- mLongScreenshotHolder.setTransitionDestinationCallback(
- (transitionDestination, onTransitionEnd) -> {
- mViewProxy.startLongScreenshotTransition(
- transitionDestination, onTransitionEnd,
- longScreenshot);
- // TODO: Do this via ActionIntentExecutor instead.
- mContext.closeSystemDialogs();
- }
- );
-
- final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
- intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE,
- owner);
- intent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
- mContext.startActivity(intent,
- ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());
- RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
- SCREENSHOT_REMOTE_RUNNER, 0, 0);
- try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionRemote(runner,
- mDisplayId);
- } catch (Exception e) {
- Log.e(TAG, "Error overriding screenshot app transition", e);
- }
- }, mMainExecutor);
+ },
+ mViewProxy::restoreNonScrollingUi,
+ mViewProxy::startLongScreenshotTransition);
}
private void withWindowAttached(Runnable action) {
@@ -912,17 +806,7 @@
/** Reset screenshot view and then call onCompleteRunnable */
private void finishDismiss() {
Log.d(TAG, "finishDismiss");
- if (mLastScrollCaptureRequest != null) {
- mLastScrollCaptureRequest.cancel(true);
- mLastScrollCaptureRequest = null;
- }
- if (mLastScrollCaptureResponse != null) {
- mLastScrollCaptureResponse.close();
- mLastScrollCaptureResponse = null;
- }
- if (mLongScreenshotFuture != null) {
- mLongScreenshotFuture.cancel(true);
- }
+ mScrollCaptureExecutor.close();
if (mCurrentRequestCallback != null) {
mCurrentRequestCallback.onFinish();
mCurrentRequestCallback = null;
@@ -935,7 +819,7 @@
private void saveScreenshotInBackground(
ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) {
ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
- requestId, screenshot.getBitmap(), screenshot.getUserHandle(), mDisplayId);
+ requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), mDisplayId);
future.addListener(() -> {
try {
ImageExporter.Result result = future.get();
@@ -943,12 +827,8 @@
logScreenshotResultStatus(result.uri, screenshot.getUserHandle());
mScreenshotHandler.resetTimeout();
if (result.uri != null) {
- final SavedImageData savedImageData = new SavedImageData();
- savedImageData.uri = result.uri;
- savedImageData.owner = screenshot.getUserHandle();
- savedImageData.imageTime = result.timestamp;
- mActionsProvider.setCompletedScreenshot(savedImageData);
- mViewProxy.setChipIntents(savedImageData);
+ mActionsProvider.setCompletedScreenshot(new ScreenshotSavedResult(
+ result.uri, screenshot.getUserOrDefault(), result.timestamp));
}
if (DEBUG_CALLBACK) {
Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
@@ -1110,10 +990,6 @@
return mDisplayManager.getDisplay(mDisplayId);
}
- private boolean allowLongScreenshots() {
- return !mIsLowRamDevice;
- }
-
private Rect getFullScreenRect() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getDisplay().getRealMetrics(displayMetrics);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index 92e933a..4fdd90b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -5,6 +5,7 @@
import android.graphics.Insets
import android.graphics.Rect
import android.net.Uri
+import android.os.Process
import android.os.UserHandle
import android.view.Display
import android.view.WindowManager.ScreenshotSource
@@ -31,6 +32,10 @@
val packageNameString: String
get() = if (topComponent == null) "" else topComponent!!.packageName
+ fun getUserOrDefault(): UserHandle {
+ return userHandle ?: Process.myUserHandle()
+ }
+
companion object {
@JvmStatic
fun fromRequest(request: ScreenshotRequest, displayId: Int = Display.DEFAULT_DISPLAY) =
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
index 796457d..3ad4075a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotRequestProcessor.kt
@@ -17,13 +17,14 @@
package com.android.systemui.screenshot
/** Processes a screenshot request sent from [ScreenshotHelper]. */
-interface ScreenshotRequestProcessor {
+fun interface ScreenshotRequestProcessor {
/**
* Inspects the incoming ScreenshotData, potentially modifying it based upon policy.
*
- * @param screenshot the screenshot to process
+ * @param original the screenshot to process
+ * @return a potentially modified screenshot data
*/
- suspend fun process(screenshot: ScreenshotData): ScreenshotData
+ suspend fun process(original: ScreenshotData): ScreenshotData
}
/** Exception thrown by [RequestProcessor] if something goes wrong. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt
new file mode 100644
index 0000000..5b6e7ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.net.Uri
+import android.os.UserHandle
+import java.text.DateFormat
+import java.util.Date
+
+/**
+ * Represents a saved screenshot, with the uri and user it was saved to as well as the time it was
+ * saved.
+ */
+data class ScreenshotSavedResult(val uri: Uri, val user: UserHandle, val imageTime: Long) {
+ val subject: String
+
+ init {
+ val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime))
+ subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate)
+ }
+
+ companion object {
+ private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 65e8457..59e38a8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -259,16 +259,8 @@
if (DEBUG_SCROLL) {
Log.d(TAG, "Showing Scroll option");
}
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName);
mScrollChip.setVisibility(VISIBLE);
- mScrollChip.setOnClickListener((v) -> {
- if (DEBUG_INPUT) {
- Log.d(TAG, "scroll chip tapped");
- }
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
- packageName);
- onClick.run();
- });
+ mScrollChip.setOnClickListener((v) -> onClick.run());
}
@Override // ViewTreeObserver.OnComputeInternalInsetsListener
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
index 2eaff86..a895b30 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
@@ -65,10 +65,9 @@
onAction: (Notification.Action) -> Unit
) {
val bitmap = data.bitmap ?: return
- val user = data.userHandle ?: return
val component = data.topComponent ?: ComponentName("", "")
- requestQuickShareAction(id, bitmap, component, user) { quickShareAction ->
- onAction(quickShareAction)
+ requestQuickShareAction(id, bitmap, component, data.getUserOrDefault()) { quickShare ->
+ onAction(quickShare)
}
}
@@ -83,14 +82,19 @@
fun requestSmartActions(
data: ScreenshotData,
id: String,
- result: ScreenshotController.SavedImageData,
+ result: ScreenshotSavedResult,
onActions: (List<Notification.Action>) -> Unit
) {
val bitmap = data.bitmap ?: return
- val user = data.userHandle ?: return
- val uri = result.uri ?: return
val component = data.topComponent ?: ComponentName("", "")
- requestSmartActions(id, bitmap, component, user, uri, REGULAR_SMART_ACTIONS) { actions ->
+ requestSmartActions(
+ id,
+ bitmap,
+ component,
+ data.getUserOrDefault(),
+ result.uri,
+ REGULAR_SMART_ACTIONS
+ ) { actions ->
onActions(actions)
}
}
@@ -102,14 +106,14 @@
* @param uri the URI of the saved screenshot
* @param subject the subject/title for the screenshot
* @param id the request ID of the screenshot
- * @return the wrapped action
+ * @return the pending intent with correct URI
*/
fun wrapIntent(
quickShare: Notification.Action,
uri: Uri,
subject: String,
id: String
- ): Notification.Action {
+ ): PendingIntent {
val wrappedIntent: Intent =
Intent(context, SmartActionsReceiver::class.java)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
@@ -130,17 +134,12 @@
.putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType)
.putExtra(ScreenshotController.EXTRA_ID, id)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, true)
- val broadcastIntent =
- PendingIntent.getBroadcast(
- context,
- Random.nextInt(),
- wrappedIntent,
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
- )
- return Notification.Action.Builder(quickShare.getIcon(), quickShare.title, broadcastIntent)
- .setContextual(true)
- .addExtras(extras)
- .build()
+ return PendingIntent.getBroadcast(
+ context,
+ Random.nextInt(),
+ wrappedIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
}
private fun createFillInIntent(uri: Uri, subject: String): Intent {
@@ -197,7 +196,7 @@
onActions(listOf())
return
}
- var smartActionsFuture: CompletableFuture<List<Notification.Action>>
+ val smartActionsFuture: CompletableFuture<List<Notification.Action>>
val startTimeMs = SystemClock.uptimeMillis()
try {
smartActionsFuture =
@@ -266,6 +265,7 @@
Log.e(TAG, "Error in notifyScreenshotOp: ", e)
}
}
+
private fun isSmartActionsEnabled(user: UserHandle): Boolean {
// Smart actions don't yet work for cross-user saves.
val savingToOtherUser = user !== Process.myUserHandle()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 92d3e55..ec7707c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -92,14 +92,14 @@
// Let's wait before logging "screenshot requested", as we should log the processed
// ScreenshotData.
val screenshotData =
- try {
- screenshotRequestProcessor.process(rawScreenshotData)
- } catch (e: RequestProcessorException) {
- Log.e(TAG, "Failed to process screenshot request!", e)
- logScreenshotRequested(rawScreenshotData)
- onFailedScreenshotRequest(rawScreenshotData, callback)
- return
- }
+ runCatching { screenshotRequestProcessor.process(rawScreenshotData) }
+ .onFailure {
+ Log.e(TAG, "Failed to process screenshot request!", it)
+ logScreenshotRequested(rawScreenshotData)
+ onFailedScreenshotRequest(rawScreenshotData, callback)
+ }
+ .getOrNull()
+ ?: return
logScreenshotRequested(screenshotData)
Log.d(TAG, "Screenshot request: $screenshotData")
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt
new file mode 100644
index 0000000..c380db0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/ChildTaskModel.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.data.model
+
+import android.content.ComponentName
+import android.graphics.Rect
+
+/** A child task within a RootTaskInfo */
+data class ChildTaskModel(
+ /** The task identifier */
+ val id: Int,
+ /** The task name */
+ val name: String,
+ /** The location and size of the task */
+ val bounds: Rect,
+ /** The user which created the task. */
+ val userId: Int,
+) {
+ val componentName: ComponentName?
+ get() = ComponentName.unflattenFromString(name)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
index 837a661..2048b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
@@ -24,6 +24,6 @@
val displayId: Int,
/** Information about the current System UI state which can affect capture. */
val systemUiState: SystemUiState,
- /** A list of root tasks on the display, ordered from bottom to top along the z-axis */
+ /** A list of root tasks on the display, ordered from top to bottom along the z-axis */
val rootTasks: List<RootTaskInfo>,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
index 9c81b32..48e813d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/repository/DisplayContentRepository.kt
@@ -18,7 +18,7 @@
import com.android.systemui.screenshot.data.model.DisplayContentModel
/** Provides information about tasks related to a display. */
-interface DisplayContentRepository {
+fun interface DisplayContentRepository {
/** Provides information about the tasks and content presented on a given display. */
suspend fun getDisplayContent(displayId: Int): DisplayContentModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
new file mode 100644
index 0000000..5e2b576
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+
+/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
+data class CaptureParameters(
+ /** How should the content be captured? */
+ val type: CaptureType,
+ /** The focused or top component at the time of the screenshot. */
+ val component: ComponentName?,
+ /** Which user should receive the screenshot file? */
+ val owner: UserHandle,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
new file mode 100644
index 0000000..4a88180
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+
+/** Contains logic to determine when and how an adjust to screenshot behavior applies. */
+fun interface CapturePolicy {
+ /**
+ * Test the policy against the current display task state. If the policy applies, Returns a
+ * non-null [CaptureParameters] describing how the screenshot request should be augmented.
+ */
+ suspend fun apply(content: DisplayContentModel): CaptureParameters?
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
similarity index 61%
copy from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java
copy to packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
index b8f9f0e..6ca2e9d6 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
@@ -14,16 +14,18 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.systemui.screenshot.policy
-import com.android.asllib.util.MalformedXmlException;
+import android.graphics.Rect
-import org.w3c.dom.Element;
+/** What to capture */
+sealed interface CaptureType {
+ /** Capture the entire screen contents. */
+ class FullScreen(val displayId: Int) : CaptureType
-import java.util.List;
-
-public interface AslMarshallableFactory<T extends AslMarshallable> {
-
- /** Creates an {@link AslMarshallableFactory} from human-readable DOM element */
- T createFromHrElements(List<Element> elements) throws MalformedXmlException;
+ /** Capture the contents of the task only. */
+ class IsolatedTask(
+ val taskId: Int,
+ val taskBounds: Rect?,
+ ) : CaptureType
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
new file mode 100644
index 0000000..2c0a0db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.content.ComponentName
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.os.UserHandle
+import android.util.Log
+import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.screenshot.ImageCapture
+import com.android.systemui.screenshot.ScreenshotData
+import com.android.systemui.screenshot.ScreenshotRequestProcessor
+import com.android.systemui.screenshot.data.repository.DisplayContentRepository
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+private const val TAG = "PolicyRequestProcessor"
+
+/** A [ScreenshotRequestProcessor] which supports general policy rule matching. */
+class PolicyRequestProcessor(
+ @Background private val background: CoroutineDispatcher,
+ private val capture: ImageCapture,
+ private val displayTasks: DisplayContentRepository,
+ private val policies: List<CapturePolicy>,
+) : ScreenshotRequestProcessor {
+ override suspend fun process(original: ScreenshotData): ScreenshotData {
+ if (original.type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+ // The request contains an already captured screenshot, accept it as is.
+ Log.i(TAG, "Screenshot bitmap provided. No modifications applied.")
+ return original
+ }
+
+ val tasks = displayTasks.getDisplayContent(original.displayId)
+
+ // If policies yield explicit modifications, apply them and return the result
+ Log.i(TAG, "Applying policy checks....")
+ policies
+ .firstNotNullOfOrNull { policy -> policy.apply(tasks) }
+ ?.let {
+ Log.i(TAG, "Modifying screenshot: $it")
+ return apply(it, original)
+ }
+
+ // Otherwise capture normally, filling in additional information as needed.
+ return replaceWithScreenshot(
+ original = original,
+ componentName = original.topComponent ?: tasks.rootTasks.firstOrNull()?.topActivity,
+ owner = original.userHandle,
+ displayId = original.displayId
+ )
+ }
+
+ /** Produce a new [ScreenshotData] using [CaptureParameters] */
+ suspend fun apply(updates: CaptureParameters, original: ScreenshotData): ScreenshotData {
+ // Update and apply bitmap capture depending on the parameters.
+ val updated =
+ when (val type = updates.type) {
+ is IsolatedTask ->
+ replaceWithTaskSnapshot(
+ original,
+ updates.component,
+ updates.owner,
+ type.taskId,
+ type.taskBounds
+ )
+ is FullScreen ->
+ replaceWithScreenshot(
+ original,
+ updates.component,
+ updates.owner,
+ type.displayId
+ )
+ }
+ return updated
+ }
+
+ suspend fun replaceWithTaskSnapshot(
+ original: ScreenshotData,
+ componentName: ComponentName?,
+ owner: UserHandle,
+ taskId: Int,
+ taskBounds: Rect?,
+ ): ScreenshotData {
+ val taskSnapshot = capture.captureTask(taskId)
+ return original.copy(
+ type = TAKE_SCREENSHOT_PROVIDED_IMAGE,
+ bitmap = taskSnapshot,
+ userHandle = owner,
+ taskId = taskId,
+ topComponent = componentName,
+ screenBounds = taskBounds
+ )
+ }
+
+ suspend fun replaceWithScreenshot(
+ original: ScreenshotData,
+ componentName: ComponentName?,
+ owner: UserHandle?,
+ displayId: Int,
+ ): ScreenshotData {
+ val screenshot = captureDisplay(displayId)
+ return original.copy(
+ type = TAKE_SCREENSHOT_FULLSCREEN,
+ bitmap = screenshot,
+ userHandle = owner,
+ topComponent = componentName,
+ screenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0)
+ )
+ }
+
+ /** TODO: Move to ImageCapture (existing function is non-suspending) */
+ private suspend fun captureDisplay(displayId: Int): Bitmap? {
+ return withContext(background) { capture.captureDisplay(displayId) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
new file mode 100644
index 0000000..221e647
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.os.UserHandle
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.android.systemui.screenshot.policy.CaptureType.FullScreen
+import javax.inject.Inject
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.firstOrNull
+
+/**
+ * Condition: When any visible task belongs to a private user.
+ *
+ * Parameters: Capture the whole screen, owned by the private user.
+ */
+class PrivateProfilePolicy
+@Inject
+constructor(
+ private val profileTypes: ProfileTypeRepository,
+) : CapturePolicy {
+ override suspend fun apply(content: DisplayContentModel): CaptureParameters? {
+ // Find the first visible rootTaskInfo with a child task owned by a private user
+ val (rootTask, childTask) =
+ content.rootTasks
+ .filter { it.isVisible }
+ .firstNotNullOfOrNull { root ->
+ root
+ .childTasksTopDown()
+ .firstOrNull {
+ profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
+ }
+ ?.let { root to it }
+ }
+ ?: return null
+
+ // If matched, return parameters needed to modify the request.
+ return CaptureParameters(
+ type = FullScreen(content.displayId),
+ component = childTask.componentName ?: rootTask.topActivity,
+ owner = UserHandle.of(childTask.userId),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
new file mode 100644
index 0000000..d2f4d9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.app.ActivityTaskManager.RootTaskInfo
+import com.android.systemui.screenshot.data.model.ChildTaskModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.coroutines.flow.map
+
+internal fun RootTaskInfo.childTasksTopDown(): Flow<ChildTaskModel> {
+ return ((numActivities - 1) downTo 0).asFlow().map { index ->
+ ChildTaskModel(
+ childTaskIds[index],
+ childTaskNames[index],
+ childTaskBounds[index],
+ childTaskUserIds[index]
+ )
+ }
+}
+
+internal suspend fun RootTaskInfo.firstChildTaskOrNull(
+ filter: suspend (Int) -> Boolean
+): Pair<RootTaskInfo, Int>? {
+ // Child tasks are provided in bottom-up order
+ // Filtering is done top-down, so iterate backwards here.
+ for (index in numActivities - 1 downTo 0) {
+ if (filter(index)) {
+ return (this to index)
+ }
+ }
+ return null
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
index bc71ab7..63d1508 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicyModule.kt
@@ -16,7 +16,9 @@
package com.android.systemui.screenshot.policy
+import com.android.systemui.Flags.screenshotPrivateProfile
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.screenshot.ImageCapture
import com.android.systemui.screenshot.RequestProcessor
import com.android.systemui.screenshot.ScreenshotPolicy
@@ -29,6 +31,7 @@
import dagger.Module
import dagger.Provides
import javax.inject.Provider
+import kotlinx.coroutines.CoroutineDispatcher
@Module
interface ScreenshotPolicyModule {
@@ -37,18 +40,42 @@
@SysUISingleton
fun bindProfileTypeRepository(impl: ProfileTypeRepositoryImpl): ProfileTypeRepository
- companion object {
- @Provides
- @SysUISingleton
- fun bindScreenshotRequestProcessor(
- imageCapture: ImageCapture,
- policyProvider: Provider<ScreenshotPolicy>,
- ): ScreenshotRequestProcessor {
- return RequestProcessor(imageCapture, policyProvider.get())
- }
- }
-
@Binds
@SysUISingleton
fun bindDisplayContentRepository(impl: DisplayContentRepositoryImpl): DisplayContentRepository
+
+ companion object {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun bindCapturePolicyList(
+ privateProfilePolicy: PrivateProfilePolicy,
+ workProfilePolicy: WorkProfilePolicy,
+ ): List<CapturePolicy> {
+ // In order of priority. The first matching policy applies.
+ return listOf(workProfilePolicy, privateProfilePolicy)
+ }
+
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun bindScreenshotRequestProcessor(
+ @Background background: CoroutineDispatcher,
+ imageCapture: ImageCapture,
+ policyProvider: Provider<ScreenshotPolicy>,
+ displayContentRepoProvider: Provider<DisplayContentRepository>,
+ policyListProvider: Provider<List<CapturePolicy>>,
+ ): ScreenshotRequestProcessor {
+ return if (screenshotPrivateProfile()) {
+ PolicyRequestProcessor(
+ background,
+ imageCapture,
+ displayContentRepoProvider.get(),
+ policyListProvider.get()
+ )
+ } else {
+ RequestProcessor(imageCapture, policyProvider.get())
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
new file mode 100644
index 0000000..d6b5d6d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.policy
+
+import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
+import android.os.UserHandle
+import com.android.systemui.screenshot.data.model.DisplayContentModel
+import com.android.systemui.screenshot.data.model.ProfileType
+import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
+import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
+import javax.inject.Inject
+import kotlinx.coroutines.flow.first
+
+/**
+ * Condition: When the top visible task (excluding PIP mode) belongs to a work user.
+ *
+ * Parameters: Capture only the foreground task, owned by the work user.
+ */
+class WorkProfilePolicy
+@Inject
+constructor(
+ private val profileTypes: ProfileTypeRepository,
+) : CapturePolicy {
+ override suspend fun apply(content: DisplayContentModel): CaptureParameters? {
+ // Find the first non PiP rootTask with a top child task owned by a work user
+ val (rootTask, childTask) =
+ content.rootTasks
+ .filter { it.isVisible && it.windowingMode != WINDOWING_MODE_PINNED }
+ .map { it to it.childTasksTopDown().first() }
+ .firstOrNull { (_, child) ->
+ profileTypes.getProfileType(child.userId) == ProfileType.WORK
+ }
+ ?: return null
+
+ // If matched, return parameters needed to modify the request.
+ return CaptureParameters(
+ type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
+ component = childTask.componentName ?: rootTask.topActivity,
+ owner = UserHandle.of(childTask.userId),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index 1e1a577..706ac9c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -335,8 +335,8 @@
// TODO: Fix transition for work profile. Omitting it in the meantime.
mActionExecutor.launchIntentAsync(
ActionIntentCreator.INSTANCE.createEdit(uri, this),
- null,
- mScreenshotUserHandle, false);
+ mScreenshotUserHandle, false,
+ /* activityOptions */ null, /* transitionCoordinator */ null);
} else {
String editorPackage = getString(R.string.config_screenshotEditor);
Intent intent = new Intent(Intent.ACTION_EDIT);
@@ -363,7 +363,8 @@
private void doShare(Uri uri) {
Intent shareIntent = ActionIntentCreator.INSTANCE.createShare(uri);
- mActionExecutor.launchIntentAsync(shareIntent, null, mScreenshotUserHandle, false);
+ mActionExecutor.launchIntentAsync(shareIntent, mScreenshotUserHandle, false,
+ /* activityOptions */ null, /* transitionCoordinator */ null);
}
private void onClicked(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt
new file mode 100644
index 0000000..6c4ee3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.scroll
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.util.Log
+import android.view.ScrollCaptureResponse
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+import java.util.concurrent.Future
+import javax.inject.Inject
+
+class ScrollCaptureExecutor
+@Inject
+constructor(
+ activityManager: ActivityManager,
+ private val scrollCaptureClient: ScrollCaptureClient,
+ private val scrollCaptureController: ScrollCaptureController,
+ private val longScreenshotHolder: LongScreenshotData,
+ @Main private val mainExecutor: Executor
+) {
+ private val isLowRamDevice = activityManager.isLowRamDevice
+ private var lastScrollCaptureRequest: ListenableFuture<ScrollCaptureResponse>? = null
+ private var lastScrollCaptureResponse: ScrollCaptureResponse? = null
+ private var longScreenshotFuture: ListenableFuture<LongScreenshot>? = null
+
+ fun requestScrollCapture(
+ displayId: Int,
+ token: IBinder,
+ callback: (ScrollCaptureResponse) -> Unit
+ ) {
+ if (!allowLongScreenshots()) {
+ Log.d(TAG, "Long screenshots not supported on this device")
+ return
+ }
+ scrollCaptureClient.setHostWindowToken(token)
+ lastScrollCaptureRequest?.cancel(true)
+ val scrollRequest =
+ scrollCaptureClient.request(displayId).apply {
+ addListener(
+ { onScrollCaptureResponseReady(this)?.let { callback.invoke(it) } },
+ mainExecutor
+ )
+ }
+ lastScrollCaptureRequest = scrollRequest
+ }
+
+ fun interface ScrollTransitionReady {
+ fun onTransitionReady(
+ destRect: Rect,
+ onTransitionEnd: Runnable,
+ longScreenshot: LongScreenshot
+ )
+ }
+
+ fun executeBatchScrollCapture(
+ response: ScrollCaptureResponse,
+ onCaptureComplete: Runnable,
+ onFailure: Runnable,
+ transition: ScrollTransitionReady,
+ ) {
+ // Clear the reference to prevent close() on reset
+ lastScrollCaptureResponse = null
+ longScreenshotFuture?.cancel(true)
+ longScreenshotFuture =
+ scrollCaptureController.run(response).apply {
+ addListener(
+ {
+ getLongScreenshotChecked(this, onFailure)?.let {
+ longScreenshotHolder.setLongScreenshot(it)
+ longScreenshotHolder.setTransitionDestinationCallback {
+ destinationRect: Rect,
+ onTransitionEnd: Runnable ->
+ transition.onTransitionReady(destinationRect, onTransitionEnd, it)
+ }
+ onCaptureComplete.run()
+ }
+ },
+ mainExecutor
+ )
+ }
+ }
+
+ fun close() {
+ lastScrollCaptureRequest?.cancel(true)
+ lastScrollCaptureRequest = null
+ lastScrollCaptureResponse?.close()
+ lastScrollCaptureResponse = null
+ longScreenshotFuture?.cancel(true)
+ }
+
+ private fun getLongScreenshotChecked(
+ future: ListenableFuture<LongScreenshot>,
+ onFailure: Runnable
+ ): LongScreenshot? {
+ var longScreenshot: LongScreenshot? = null
+ runCatching { longScreenshot = future.get() }
+ .onFailure {
+ Log.e(TAG, "Caught exception", it)
+ onFailure.run()
+ return null
+ }
+ if (longScreenshot?.height != 0) {
+ return longScreenshot
+ }
+ onFailure.run()
+ return null
+ }
+
+ private fun onScrollCaptureResponseReady(
+ responseFuture: Future<ScrollCaptureResponse>
+ ): ScrollCaptureResponse? {
+ try {
+ lastScrollCaptureResponse?.close()
+ lastScrollCaptureResponse = null
+ if (responseFuture.isCancelled) {
+ return null
+ }
+ val captureResponse = responseFuture.get().apply { lastScrollCaptureResponse = this }
+ if (!captureResponse.isConnected) {
+ // No connection means that the target window wasn't found
+ // or that it cannot support scroll capture.
+ Log.d(
+ TAG,
+ "ScrollCapture: ${captureResponse.description} [${captureResponse.windowTitle}]"
+ )
+ return null
+ }
+ Log.d(TAG, "ScrollCapture: connected to window [${captureResponse.windowTitle}]")
+ return captureResponse
+ } catch (e: InterruptedException) {
+ Log.e(TAG, "requestScrollCapture interrupted", e)
+ } catch (e: ExecutionException) {
+ Log.e(TAG, "requestScrollCapture failed", e)
+ }
+ return null
+ }
+
+ private fun allowLongScreenshots(): Boolean {
+ return !isLowRamDevice
+ }
+
+ private companion object {
+ private const val TAG = "ScrollCaptureExecutor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
index c7fe3f6..3c5a0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
@@ -28,15 +28,16 @@
fun bind(view: View, viewModel: ActionButtonViewModel) {
val iconView = view.requireViewById<ImageView>(R.id.overlay_action_chip_icon)
val textView = view.requireViewById<TextView>(R.id.overlay_action_chip_text)
- iconView.setImageDrawable(viewModel.icon)
- textView.text = viewModel.name
- setMargins(iconView, textView, viewModel.name?.isNotEmpty() ?: false)
+ iconView.setImageDrawable(viewModel.appearance.icon)
+ textView.text = viewModel.appearance.label
+ setMargins(iconView, textView, viewModel.appearance.label?.isNotEmpty() ?: false)
if (viewModel.onClicked != null) {
view.setOnClickListener { viewModel.onClicked.invoke() }
} else {
view.setOnClickListener(null)
}
- view.contentDescription = viewModel.description
+ view.tag = viewModel.id
+ view.contentDescription = viewModel.appearance.description
view.visibility = View.VISIBLE
view.alpha = 1f
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index b191a1a..32e9296 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -16,7 +16,6 @@
package com.android.systemui.screenshot.ui.binder
-import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -71,21 +70,36 @@
.requireViewById<View>(R.id.actions_container_background)
.visibility = View.VISIBLE
}
- val viewPool = actionsContainer.children.toList()
- actionsContainer.removeAllViews()
- val actionButtons =
- List(actions.size) {
- viewPool.getOrElse(it) {
+
+ // Remove any buttons not in the new list, then do another pass to add
+ // any new actions and update any that are already there.
+ // This assumes that actions can never change order and that each action
+ // ID is unique.
+ val newIds = actions.map { it.id }
+
+ for (view in actionsContainer.children.toList()) {
+ if (view.tag !in newIds) {
+ actionsContainer.removeView(view)
+ }
+ }
+
+ for ((index, action) in actions.withIndex()) {
+ val currentView: View? = actionsContainer.getChildAt(index)
+ if (action.id == currentView?.tag) {
+ // Same ID, update the display
+ ActionButtonViewBinder.bind(currentView, action)
+ } else {
+ // Different ID. Removals have already happened so this must
+ // mean that the new action must be inserted here.
+ val actionButton =
layoutInflater.inflate(
R.layout.overlay_action_chip,
actionsContainer,
false
)
- }
+ actionsContainer.addView(actionButton, index)
+ ActionButtonViewBinder.bind(actionButton, action)
}
- actionButtons.zip(actions).forEach {
- actionsContainer.addView(it.first)
- ActionButtonViewBinder.bind(it.first, it.second)
}
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
similarity index 67%
copy from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java
copy to packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
index 4e64ab0..55a2ad2 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.systemui.screenshot.ui.viewmodel
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
+import android.graphics.drawable.Drawable
-import java.util.List;
-
-public interface AslMarshallable {
-
- /** Creates the on-device DOM element from the AslMarshallable Java Object. */
- List<Element> toOdDomElements(Document doc);
-}
+/** Data describing how an action should be shown to the user. */
+data class ActionButtonAppearance(
+ val icon: Drawable?,
+ val label: CharSequence?,
+ val description: CharSequence,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
index 05bfed1..64b0105 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
@@ -16,11 +16,19 @@
package com.android.systemui.screenshot.ui.viewmodel
-import android.graphics.drawable.Drawable
-
data class ActionButtonViewModel(
- val icon: Drawable?,
- val name: CharSequence?,
- val description: CharSequence,
- val onClicked: (() -> Unit)?
-)
+ val appearance: ActionButtonAppearance,
+ val id: Int,
+ val onClicked: (() -> Unit)?,
+) {
+ companion object {
+ private var nextId = 0
+
+ private fun getId() = nextId.also { nextId += 1 }
+
+ fun withNextId(
+ appearance: ActionButtonAppearance,
+ onClicked: (() -> Unit)?
+ ): ActionButtonViewModel = ActionButtonViewModel(appearance, getId(), onClicked)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
index ddfa69b..fa34803 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.screenshot.ui.viewmodel
import android.graphics.Bitmap
+import android.util.Log
import android.view.accessibility.AccessibilityManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -39,16 +40,34 @@
_previewAction.value = onClick
}
- fun addAction(action: ActionButtonViewModel) {
+ fun addAction(actionAppearance: ActionButtonAppearance, onClicked: (() -> Unit)): Int {
val actionList = _actions.value.toMutableList()
+ val action = ActionButtonViewModel.withNextId(actionAppearance, onClicked)
actionList.add(action)
_actions.value = actionList
+ return action.id
}
- fun addActions(actions: List<ActionButtonViewModel>) {
+ fun updateActionAppearance(actionId: Int, appearance: ActionButtonAppearance) {
val actionList = _actions.value.toMutableList()
- actionList.addAll(actions)
- _actions.value = actionList
+ val index = actionList.indexOfFirst { it.id == actionId }
+ if (index >= 0) {
+ actionList[index] =
+ ActionButtonViewModel(appearance, actionId, actionList[index].onClicked)
+ _actions.value = actionList
+ } else {
+ Log.w(TAG, "Attempted to update unknown action id $actionId")
+ }
+ }
+
+ fun removeAction(actionId: Int) {
+ val actionList = _actions.value.toMutableList()
+ if (actionList.removeIf { it.id == actionId }) {
+ // Update if something was removed.
+ _actions.value = actionList
+ } else {
+ Log.w(TAG, "Attempted to remove unknown action id $actionId")
+ }
}
fun reset() {
@@ -56,4 +75,8 @@
_previewAction.value = null
_actions.value = listOf()
}
+
+ companion object {
+ const val TAG = "ScreenshotViewModel"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 92d6ec97..8397d9f 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -52,7 +52,6 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.util.settings.SecureSettings;
import dagger.assisted.Assisted;
@@ -107,7 +106,7 @@
private ValueAnimator mSliderAnimator;
@Override
- public void setMirror(BrightnessMirrorController controller) {
+ public void setMirror(@Nullable MirrorController controller) {
mControl.setMirrorControllerAndMirror(controller);
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
index 701d814..073279b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt
@@ -16,12 +16,9 @@
package com.android.systemui.settings.brightness
-import com.android.systemui.statusbar.policy.BrightnessMirrorController
-import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener
-
class BrightnessMirrorHandler(brightnessController: MirroredBrightnessController) {
- var mirrorController: BrightnessMirrorController? = null
+ var mirrorController: MirrorController? = null
private set
var brightnessController: MirroredBrightnessController = brightnessController
@@ -30,7 +27,8 @@
updateBrightnessMirror()
}
- private val brightnessMirrorListener = BrightnessMirrorListener { updateBrightnessMirror() }
+ private val brightnessMirrorListener =
+ MirrorController.BrightnessMirrorListener { updateBrightnessMirror() }
fun onQsPanelAttached() {
mirrorController?.addCallback(brightnessMirrorListener)
@@ -40,7 +38,7 @@
mirrorController?.removeCallback(brightnessMirrorListener)
}
- fun setController(controller: BrightnessMirrorController?) {
+ fun setController(controller: MirrorController?) {
mirrorController?.removeCallback(brightnessMirrorListener)
mirrorController = controller
mirrorController?.addCallback(brightnessMirrorListener)
@@ -48,6 +46,6 @@
}
private fun updateBrightnessMirror() {
- mirrorController?.let { brightnessController.setMirror(it) }
+ brightnessController.setMirror(mirrorController)
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 539b0c2..b425fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -57,8 +57,10 @@
ToggleSlider {
private Listener mListener;
+ @Nullable
private ToggleSlider mMirror;
- private BrightnessMirrorController mMirrorController;
+ @Nullable
+ private MirrorController mMirrorController;
private boolean mTracking;
private final FalsingManager mFalsingManager;
private final UiEventLogger mUiEventLogger;
@@ -108,6 +110,9 @@
protected void onViewAttached() {
mView.setOnSeekBarChangeListener(mSeekListener);
mView.setOnInterceptListener(mOnInterceptListener);
+ if (mMirror != null) {
+ mView.setOnDispatchTouchEventListener(this::mirrorTouchEvent);
+ }
}
@Override
@@ -129,7 +134,10 @@
private boolean copyEventToMirror(MotionEvent ev) {
MotionEvent copy = ev.copy();
- boolean out = mMirror.mirrorTouchEvent(copy);
+ boolean out = false;
+ if (mMirror != null) {
+ out = mMirror.mirrorTouchEvent(copy);
+ }
copy.recycle();
return out;
}
@@ -166,9 +174,13 @@
* @param c
*/
@Override
- public void setMirrorControllerAndMirror(BrightnessMirrorController c) {
+ public void setMirrorControllerAndMirror(@Nullable MirrorController c) {
mMirrorController = c;
- setMirror(c.getToggleSlider());
+ if (c != null) {
+ setMirror(c.getToggleSlider());
+ } else {
+ setMirror(null);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt
new file mode 100644
index 0000000..6a9af26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirrorController.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness
+
+import android.view.View
+import com.android.systemui.settings.brightness.MirrorController.BrightnessMirrorListener
+import com.android.systemui.statusbar.policy.CallbackController
+
+interface MirrorController : CallbackController<BrightnessMirrorListener> {
+
+ /**
+ * Get the [ToggleSlider] currently associated with this controller, or `null` if none currently
+ */
+ fun getToggleSlider(): ToggleSlider?
+
+ /**
+ * Indicate to this controller that the user is dragging on the brightness view and the mirror
+ * should show
+ */
+ fun showMirror()
+
+ /**
+ * Indicate to this controller that the user has stopped dragging on the brightness view and the
+ * mirror should hide
+ */
+ fun hideMirror()
+
+ /**
+ * Set the location and size of the current brightness [view] in QS so it can be properly
+ * adapted to show the mirror in the same location and with the same size.
+ */
+ fun setLocationAndSize(view: View)
+
+ fun interface BrightnessMirrorListener {
+ fun onBrightnessMirrorReinflated(brightnessMirror: View?)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
index 8d857de..b1a532b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt
@@ -22,5 +22,5 @@
* Indicates controller that has brightness slider and uses [BrightnessMirrorController]
*/
interface MirroredBrightnessController {
- fun setMirror(controller: BrightnessMirrorController)
+ fun setMirror(controller: MirrorController?)
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
index 648e33b..24bc670 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
@@ -19,7 +19,6 @@
import android.view.MotionEvent;
import com.android.settingslib.RestrictedLockUtils;
-import com.android.systemui.statusbar.policy.BrightnessMirrorController;
public interface ToggleSlider {
interface Listener {
@@ -27,7 +26,7 @@
}
void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin);
- void setMirrorControllerAndMirror(BrightnessMirrorController c);
+ void setMirrorControllerAndMirror(MirrorController c);
boolean mirrorTouchEvent(MotionEvent ev);
void setOnChangedListener(Listener l);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt
new file mode 100644
index 0000000..a0c9be4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class BrightnessMirrorShowingRepository @Inject constructor() {
+ private val _isShowing = MutableStateFlow(false)
+ val isShowing = _isShowing.asStateFlow()
+
+ fun setMirrorShowing(showing: Boolean) {
+ _isShowing.value = showing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt
new file mode 100644
index 0000000..ef6e72f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.settings.brightness.data.repository.BrightnessMirrorShowingRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class BrightnessMirrorShowingInteractor
+@Inject
+constructor(
+ private val brightnessMirrorShowingRepository: BrightnessMirrorShowingRepository,
+) {
+ val isShowing = brightnessMirrorShowingRepository.isShowing
+
+ fun setMirrorShowing(showing: Boolean) {
+ brightnessMirrorShowingRepository.setMirrorShowing(showing)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt
new file mode 100644
index 0000000..468a873
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.binder
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.BrightnessSliderController
+
+object BrightnessMirrorInflater {
+
+ fun inflate(
+ context: Context,
+ sliderControllerFactory: BrightnessSliderController.Factory,
+ ): Pair<View, BrightnessSliderController> {
+ val frame =
+ (LayoutInflater.from(context).inflate(R.layout.brightness_mirror_container, null)
+ as ViewGroup)
+ .apply { isVisible = true }
+ val sliderController = sliderControllerFactory.create(context, frame)
+ sliderController.init()
+ frame.addView(
+ sliderController.rootView,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ return frame to sliderController
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
new file mode 100644
index 0000000..2651a994
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.viewModel
+
+import android.content.res.Resources
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.settings.brightness.MirrorController
+import com.android.systemui.settings.brightness.ToggleSlider
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class BrightnessMirrorViewModel
+@Inject
+constructor(
+ private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
+ @Main private val resources: Resources,
+ val sliderControllerFactory: BrightnessSliderController.Factory,
+) : MirrorController {
+
+ private val tempPosition = IntArray(2)
+
+ private var _toggleSlider: BrightnessSliderController? = null
+
+ val isShowing = brightnessMirrorShowingInteractor.isShowing
+
+ private val _locationAndSize: MutableStateFlow<LocationAndSize> =
+ MutableStateFlow(LocationAndSize())
+ val locationAndSize = _locationAndSize.asStateFlow()
+
+ override fun getToggleSlider(): ToggleSlider? {
+ return _toggleSlider
+ }
+
+ fun setToggleSlider(toggleSlider: BrightnessSliderController) {
+ _toggleSlider = toggleSlider
+ }
+
+ override fun showMirror() {
+ brightnessMirrorShowingInteractor.setMirrorShowing(true)
+ }
+
+ override fun hideMirror() {
+ brightnessMirrorShowingInteractor.setMirrorShowing(false)
+ }
+
+ override fun setLocationAndSize(view: View) {
+ view.getLocationInWindow(tempPosition)
+ val padding = resources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+ _toggleSlider?.rootView?.setPadding(padding, padding, padding, padding)
+ // Account for desired padding
+ _locationAndSize.value =
+ LocationAndSize(
+ yOffset = tempPosition[1] - padding,
+ width = view.measuredWidth + 2 * padding,
+ height = view.measuredHeight + 2 * padding,
+ )
+ }
+
+ // Callbacks are used for indicating reinflation when the config changes in some ways (like
+ // density). However, we don't need that as we recompose the view anyway
+ override fun addCallback(listener: MirrorController.BrightnessMirrorListener) {}
+
+ override fun removeCallback(listener: MirrorController.BrightnessMirrorListener) {}
+}
+
+data class LocationAndSize(
+ val yOffset: Int = 0,
+ val width: Int = 0,
+ val height: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 89e8413..fb32b9f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -417,6 +417,12 @@
} else {
mLpChanged.flags &= ~LayoutParams.FLAG_SECURE;
}
+
+ if (state.bouncerShowing) {
+ mLpChanged.inputFeatures |= LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING;
+ } else {
+ mLpChanged.inputFeatures &= ~LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING;
+ }
}
protected boolean isDebuggable() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index f9b4e67..903af61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -81,6 +81,8 @@
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
+ mInteractionEventHandler.collectKeyEvent(event);
+
if (mInteractionEventHandler.interceptMediaKey(event)) {
return true;
}
@@ -301,6 +303,11 @@
boolean dispatchKeyEvent(KeyEvent event);
boolean dispatchKeyEventPreIme(KeyEvent event);
+
+ /**
+ * Collects the KeyEvent without intercepting it
+ */
+ void collectKeyEvent(KeyEvent event);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 324dfdf..6ac81d2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -563,6 +563,11 @@
public boolean dispatchKeyEvent(KeyEvent event) {
return mSysUIKeyEventHandler.dispatchKeyEvent(event);
}
+
+ @Override
+ public void collectKeyEvent(KeyEvent event) {
+ mFalsingCollector.onKeyEvent(event);
+ }
});
mView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
index b8250cc..3462993 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import javax.inject.Inject
@@ -28,7 +27,6 @@
constructor(
private val shadeInteractor: ShadeInteractor,
private val qsSceneAdapter: QSSceneAdapter,
- private val qsContainerController: QSContainerController,
) : QuickSettingsController {
override val expanded: Boolean
@@ -43,7 +41,7 @@
}
override fun closeQsCustomizer() {
- qsContainerController.setCustomizerShowing(false)
+ qsSceneAdapter.requestCloseCustomizer()
}
@Deprecated("specific to legacy split shade")
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 29cdf0a..f5dd5e4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -22,6 +22,7 @@
import android.view.LayoutInflater
import android.view.ViewStub
import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.compose.animation.scene.SceneKey
import com.android.keyguard.logging.ScrimLogger
import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
@@ -86,6 +87,7 @@
sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
): WindowRootView {
return if (sceneContainerFlags.isEnabled()) {
+ checkNoSceneDuplicates(scenesProvider.get())
val sceneWindowRootView =
layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
sceneWindowRootView.init(
@@ -281,5 +283,21 @@
): StatusIconContainer {
return header.requireViewById(R.id.statusIcons)
}
+
+ private fun checkNoSceneDuplicates(scenes: Set<Scene>) {
+ val keys = mutableSetOf<SceneKey>()
+ val duplicates = mutableSetOf<SceneKey>()
+ scenes
+ .map { it.key }
+ .forEach { sceneKey ->
+ if (keys.contains(sceneKey)) {
+ duplicates.add(sceneKey)
+ } else {
+ keys.add(sceneKey)
+ }
+ }
+
+ check(duplicates.isEmpty()) { "Duplicate scenes detected: $duplicates" }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 9362cd0..980f665a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -33,6 +33,7 @@
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
@@ -57,6 +58,7 @@
val qsSceneAdapter: QSSceneAdapter,
val shadeHeaderViewModel: ShadeHeaderViewModel,
val notifications: NotificationsPlaceholderViewModel,
+ val brightnessMirrorViewModel: BrightnessMirrorViewModel,
val mediaDataManager: MediaDataManager,
shadeInteractor: ShadeInteractor,
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
diff --git a/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt b/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt
index 5e57f1d..8eed097 100644
--- a/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/startable/Dependencies.kt
@@ -27,4 +27,4 @@
@MustBeDocumented
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
-annotation class Dependencies(vararg val value: KClass<out CoreStartable> = [])
+annotation class Dependencies(vararg val value: KClass<*> = [])
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 8104755..d2fe20d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -23,6 +23,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.startable.Dependencies;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.lang.annotation.Retention;
@@ -30,6 +31,7 @@
/**
* Sends updates to {@link StateListener}s about changes to the status bar state and dozing state
*/
+@Dependencies(CentralSurfaces.class)
public interface SysuiStatusBarStateController extends StatusBarStateController, CoreStartable {
// TODO: b/115739177 (remove this explicit ordering if we can)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index adcbbfb..968b591 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -45,6 +45,7 @@
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.row.FooterViewButton;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.util.DrawableDumpKt;
@@ -102,6 +103,11 @@
setClearAllButtonVisible(visible, animate, /* onAnimationEnded = */ null);
}
+ /** Set the visibility of the "Manage"/"History" button to {@code visible}. */
+ public void setManageOrHistoryButtonVisible(boolean visible) {
+ mManageOrHistoryButton.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
/**
* Set the visibility of the "Clear all" button to {@code visible}. Animate the change if
* {@code animate} is true.
@@ -274,14 +280,23 @@
/** Show a message instead of the footer buttons. */
public void setFooterLabelVisible(boolean isVisible) {
- if (isVisible) {
- mManageOrHistoryButton.setVisibility(View.GONE);
- mClearAllButton.setVisibility(View.GONE);
- mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
+ // In the refactored code, hiding the buttons is handled in the FooterViewModel
+ if (FooterViewRefactor.isEnabled()) {
+ if (isVisible) {
+ mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
+ } else {
+ mSeenNotifsFooterTextView.setVisibility(View.GONE);
+ }
} else {
- mManageOrHistoryButton.setVisibility(View.VISIBLE);
- mClearAllButton.setVisibility(View.VISIBLE);
- mSeenNotifsFooterTextView.setVisibility(View.GONE);
+ if (isVisible) {
+ mManageOrHistoryButton.setVisibility(View.GONE);
+ mClearAllButton.setVisibility(View.GONE);
+ mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
+ } else {
+ mManageOrHistoryButton.setVisibility(View.VISIBLE);
+ mClearAllButton.setVisibility(View.VISIBLE);
+ mSeenNotifsFooterTextView.setVisibility(View.GONE);
+ }
}
}
@@ -443,6 +458,12 @@
*/
public boolean hideContent;
+ /**
+ * When true, skip animating Y on the next #animateTo.
+ * Once true, remains true until reset in #animateTo.
+ */
+ public boolean resetY = false;
+
@Override
public void copyFrom(ViewState viewState) {
super.copyFrom(viewState);
@@ -459,5 +480,17 @@
footerView.setContentVisibleAnimated(!hideContent);
}
}
+
+ @Override
+ public void animateTo(View child, AnimationProperties properties) {
+ if (child instanceof FooterView) {
+ // Must set animateY=false before super.animateTo, which checks for animateY
+ if (resetY) {
+ properties.getAnimationFilter().animateY = false;
+ resetY = false;
+ }
+ }
+ super.animateTo(child, properties);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 65ab4fd..637cadd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -141,8 +141,12 @@
}
}
- // NOTE: The manage/history button is always visible as long as the footer is visible, no
- // need to update the visibility here.
+ launch {
+ viewModel.manageOrHistoryButton.isVisible.collect { isVisible ->
+ // NOTE: This visibility change is never animated.
+ footer.setManageOrHistoryButtonVisible(isVisible.value)
+ }
+ }
}
private suspend fun bindMessage(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index b23ef35..90fb728 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -34,6 +34,7 @@
import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -45,12 +46,33 @@
seenNotificationsInteractor: SeenNotificationsInteractor,
shadeInteractor: ShadeInteractor,
) {
+ /** A message to show instead of the footer buttons. */
+ val message: FooterMessageViewModel =
+ FooterMessageViewModel(
+ messageId = R.string.unlock_to_see_notif_text,
+ iconId = R.drawable.ic_friction_lock_closed,
+ isVisible = seenNotificationsInteractor.hasFilteredOutSeenNotifications,
+ )
+
+ private val clearAllButtonVisible =
+ activeNotificationsInteractor.hasClearableNotifications
+ .combine(message.isVisible) { hasClearableNotifications, isMessageVisible ->
+ if (isMessageVisible) {
+ // If the message is visible, the button never is
+ false
+ } else {
+ hasClearableNotifications
+ }
+ }
+ .distinctUntilChanged()
+
+ /** The button for clearing notifications. */
val clearAllButton: FooterButtonViewModel =
FooterButtonViewModel(
labelId = flowOf(R.string.clear_all_notifications_text),
accessibilityDescriptionId = flowOf(R.string.accessibility_clear_all),
isVisible =
- activeNotificationsInteractor.hasClearableNotifications
+ clearAllButtonVisible
.sample(
// TODO(b/322167853): This check is currently duplicated in
// NotificationListViewModel, but instead it should be a field in
@@ -61,9 +83,9 @@
::Pair
)
.onStart { emit(Pair(false, false)) }
- ) { hasClearableNotifications, (isShadeFullyExpanded, animationsEnabled) ->
+ ) { clearAllButtonVisible, (isShadeFullyExpanded, animationsEnabled) ->
val shouldAnimate = isShadeFullyExpanded && animationsEnabled
- AnimatableEvent(hasClearableNotifications, shouldAnimate)
+ AnimatableEvent(clearAllButtonVisible, shouldAnimate)
}
.toAnimatedValueFlow(),
)
@@ -77,18 +99,16 @@
else R.string.manage_notifications_text
}
+ /** The button for managing notification settings or opening notification history. */
val manageOrHistoryButton: FooterButtonViewModel =
FooterButtonViewModel(
labelId = manageOrHistoryButtonText,
accessibilityDescriptionId = manageOrHistoryButtonText,
- isVisible = flowOf(AnimatedValue.NotAnimating(true)),
- )
-
- val message: FooterMessageViewModel =
- FooterMessageViewModel(
- messageId = R.string.unlock_to_see_notif_text,
- iconId = R.drawable.ic_friction_lock_closed,
- isVisible = seenNotificationsInteractor.hasFilteredOutSeenNotifications,
+ isVisible =
+ // Hide the manage button if the message is visible
+ message.isVisible.map { messageVisible ->
+ AnimatedValue.NotAnimating(!messageVisible)
+ },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index eb6c7b5..9e0b16c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1767,7 +1767,7 @@
*/
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
this(context, attrs, context);
- Log.wtf(TAG, "This constructor shouldn't be called");
+ Log.e(TAG, "This constructor shouldn't be called");
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
index 03a1082..0c248f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
@@ -30,7 +30,7 @@
public static final int NO_DELAY = -1;
boolean animateAlpha;
boolean animateX;
- boolean animateY;
+ public boolean animateY;
ArraySet<View> animateYViews = new ArraySet<>();
boolean animateZ;
boolean animateHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 5dc37e0..92c597c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -56,6 +56,7 @@
import com.android.systemui.statusbar.notification.row.HybridGroupManager;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -1429,7 +1430,7 @@
if (singleLineView != null) {
minExpandHeight += singleLineView.getHeight();
} else {
- if (AsyncGroupHeaderViewInflation.isEnabled()) {
+ if (AsyncHybridViewInflation.isEnabled()) {
minExpandHeight += mMinSingleLineHeight;
} else {
Log.e(TAG, "getMinHeight: child " + child.getEntry().getKey()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index fedb88d..82559de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1868,6 +1868,13 @@
private void updateImeInset(WindowInsets windowInsets) {
mImeInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom;
+ if (mFooterView != null && mFooterView.getViewState() != null) {
+ // Do not animate footer Y when showing IME so that after IME hides, the footer
+ // appears at the correct Y. Once resetY is true, it remains true (even when IME
+ // hides, where mImeInset=0) until reset in FooterViewState#animateTo.
+ ((FooterView.FooterViewState) mFooterView.getViewState()).resetY |= mImeInset > 0;
+ }
+
if (mForcedScroll != null) {
updateForcedScroll();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
index 7e3e724..832e690 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ShadeScrimBounds.kt
@@ -30,11 +30,15 @@
/** The current height of the notification container. */
val height: Float = bottom - top
- operator fun minus(position: ViewPosition) =
- ShadeScrimBounds(
- left = left - position.left,
- top = top - position.top,
- right = right - position.left,
- bottom = bottom - position.top,
- )
+ fun minus(leftOffset: Int = 0, topOffset: Int = 0) =
+ if (leftOffset == 0 && topOffset == 0) {
+ this
+ } else {
+ ShadeScrimBounds(
+ left = this.left - leftOffset,
+ top = this.top - topOffset,
+ right = this.right - leftOffset,
+ bottom = this.bottom - topOffset,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index a98717a..047b560 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
-import android.view.View
+import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.common.ui.ConfigurationState
@@ -26,7 +26,6 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
-import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationScrollViewModel
import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -37,9 +36,6 @@
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
/** Binds the [NotificationScrollView]. */
@@ -54,8 +50,15 @@
private val configuration: ConfigurationState,
) : FlowDumperImpl(dumpManager) {
- private val viewPosition = MutableStateFlow(ViewPosition()).dumpValue("viewPosition")
- private val viewTopOffset = viewPosition.map { it.top }.distinctUntilChanged()
+ private val viewLeftOffset = MutableStateFlow(0).dumpValue("viewLeftOffset")
+
+ private fun updateViewPosition() {
+ val trueView = view.asView()
+ if (trueView.top != 0) {
+ Log.w("NSSL", "Expected $trueView to have top==0")
+ }
+ viewLeftOffset.value = trueView.left
+ }
fun bindWhileAttached(): DisposableHandle {
return view.asView().repeatWhenAttached(mainImmediateDispatcher) {
@@ -65,20 +68,20 @@
suspend fun bind() = coroutineScope {
launchAndDispose {
- viewPosition.value = view.asView().position
- view.asView().onLayoutChanged { viewPosition.value = it.position }
+ updateViewPosition()
+ view.asView().onLayoutChanged { updateViewPosition() }
}
launch {
- viewModel.shadeScrimShape(scrimRadius, viewPosition).collect {
- view.setScrimClippingShape(it)
- }
+ viewModel
+ .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset)
+ .collect { view.setScrimClippingShape(it) }
}
- launch { viewModel.stackTop.minusTopOffset().collect { view.setStackTop(it) } }
- launch { viewModel.stackBottom.minusTopOffset().collect { view.setStackBottom(it) } }
+ launch { viewModel.stackTop.collect { view.setStackTop(it) } }
+ launch { viewModel.stackBottom.collect { view.setStackBottom(it) } }
launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } }
- launch { viewModel.headsUpTop.minusTopOffset().collect { view.setHeadsUpTop(it) } }
+ launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } }
launch { viewModel.expandFraction.collect { view.setExpandFraction(it) } }
launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
@@ -94,15 +97,7 @@
}
}
- /** Combine with the topOffset flow and subtract that value from this flow's value */
- private fun Flow<Float>.minusTopOffset() =
- combine(viewTopOffset) { y, topOffset -> y - topOffset }
-
/** flow of the scrim clipping radius */
private val scrimRadius: Flow<Int>
get() = configuration.getDimensionPixelOffset(R.dimen.notification_scrim_corner_radius)
-
- /** Construct a [ViewPosition] from this view using [View.getLeft] and [View.getTop] */
- private val View.position
- get() = ViewPosition(left = left, top = top)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index cfd19ba..741102b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -21,11 +21,14 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.communalHub
+import com.android.systemui.common.ui.view.onApplyWindowInsets
+import com.android.systemui.common.ui.view.onLayoutChanged
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -137,10 +140,12 @@
}
}
- launch {
- burnInParams
- .flatMapLatest { params -> viewModel.translationY(params) }
- .collect { y -> controller.setTranslationY(y) }
+ if (!SceneContainerFlag.isEnabled) {
+ launch {
+ burnInParams
+ .flatMapLatest { params -> viewModel.translationY(params) }
+ .collect { y -> controller.setTranslationY(y) }
+ }
}
launch { viewModel.translationX.collect { x -> controller.translationX = x } }
@@ -168,22 +173,16 @@
controller.setOnHeightChangedRunnable { viewModel.notificationStackChanged() }
disposables += DisposableHandle { controller.setOnHeightChangedRunnable(null) }
- view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
- val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
- burnInParams.update { current ->
- current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ disposables +=
+ view.onApplyWindowInsets { _: View, insets: WindowInsets ->
+ val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+ burnInParams.update { current ->
+ current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ }
+ insets
}
- insets
- }
- disposables += DisposableHandle { view.setOnApplyWindowInsetsListener(null) }
- // Required to capture keyguard media changes and ensure the notification count is correct
- val layoutChangeListener =
- View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
- viewModel.notificationStackChanged()
- }
- view.addOnLayoutChangeListener(layoutChangeListener)
- disposables += DisposableHandle { view.removeOnLayoutChangeListener(layoutChangeListener) }
+ disposables += view.onLayoutChanged { viewModel.notificationStackChanged() }
return disposables
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 9483f33..516ec31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -27,7 +27,6 @@
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimClipping
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
-import com.android.systemui.statusbar.notification.stack.shared.model.ViewPosition
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -92,12 +91,12 @@
fun shadeScrimShape(
cornerRadius: Flow<Int>,
- viewPosition: Flow<ViewPosition>
+ viewLeftOffset: Flow<Int>
): Flow<ShadeScrimShape?> =
- combine(shadeScrimClipping, cornerRadius, viewPosition) { clipping, radius, position ->
+ combine(shadeScrimClipping, cornerRadius, viewLeftOffset) { clipping, radius, leftOffset ->
if (clipping == null) return@combine null
ShadeScrimShape(
- bounds = clipping.bounds - position,
+ bounds = clipping.bounds.minus(leftOffset = leftOffset),
topRadius = radius.takeIf { clipping.rounding.isTopRounded } ?: 0,
bottomRadius = radius.takeIf { clipping.rounding.isBottomRounded } ?: 0
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 692368d..0486ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -19,6 +19,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import androidx.annotation.VisibleForTesting
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -166,23 +167,35 @@
)
.dumpWhileCollecting("isShadeLocked")
+ @VisibleForTesting
+ val paddingTopDimen: Flow<Int> =
+ interactor.configurationBasedDimensions
+ .map {
+ when {
+ !it.useSplitShade -> 0
+ it.useLargeScreenHeader -> it.marginTopLargeScreen
+ else -> it.marginTop
+ }
+ }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("paddingTopDimen")
+
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
interactor.configurationBasedDimensions
.map {
val marginTop =
- if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop
+ when {
+ // y position of the NSSL in the window needs to be 0 under scene container
+ SceneContainerFlag.isEnabled -> 0
+ it.useLargeScreenHeader -> it.marginTopLargeScreen
+ else -> it.marginTop
+ }
ConfigurationBasedDimensions(
marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
marginEnd = it.marginHorizontal,
marginBottom = it.marginBottom,
marginTop = marginTop,
useSplitShade = it.useSplitShade,
- paddingTop =
- if (it.useSplitShade) {
- marginTop
- } else {
- 0
- },
)
}
.distinctUntilChanged()
@@ -341,16 +354,16 @@
combine(
isOnLockscreenWithoutShade,
keyguardInteractor.notificationContainerBounds,
- configurationBasedDimensions,
+ paddingTopDimen,
interactor.topPosition
.sampleCombine(
keyguardTransitionInteractor.isInTransitionToAnyState,
shadeInteractor.qsExpansion,
)
.onStart { emit(Triple(0f, false, 0f)) }
- ) { onLockscreen, bounds, config, (top, isInTransitionToAnyState, qsExpansion) ->
+ ) { onLockscreen, bounds, paddingTop, (top, isInTransitionToAnyState, qsExpansion) ->
if (onLockscreen) {
- bounds.copy(top = bounds.top - config.paddingTop)
+ bounds.copy(top = bounds.top - paddingTop)
} else {
// When QS expansion > 0, it should directly set the top padding so do not
// animate it
@@ -367,10 +380,6 @@
initialValue = NotificationContainerBounds(),
)
.dumpValue("bounds")
- get() {
- /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode()
- return field
- }
/**
* Ensure view is visible when the shade/qs are expanded. Also, as QS is expanding, fade out
@@ -546,6 +555,8 @@
* translated as the keyguard fades out.
*/
fun translationY(params: BurnInParameters): Flow<Float> {
+ // with SceneContainer, x translation is handled by views, y is handled by compose
+ SceneContainerFlag.assertInLegacyMode()
return combine(
aodBurnInViewModel
.movement(params)
@@ -644,6 +655,5 @@
val marginEnd: Int,
val marginBottom: Int,
val useSplitShade: Boolean,
- val paddingTop: Int,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index a1fae03..2651d2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -103,8 +103,8 @@
/**
* Returns an ActivityOptions bundle created using the given parameters.
*
- * @param displayId The ID of the display to launch the activity in. Typically this would
- * be the display the status bar is on.
+ * @param displayId The ID of the display to launch the activity in. Typically this would
+ * be the display the status bar is on.
* @param animationAdapter The animation adapter used to start this activity, or {@code null}
* for the default animation.
*/
@@ -197,7 +197,7 @@
void onKeyguardViewManagerStatesUpdated();
- /** */
+ /** */
boolean getCommandQueuePanelsEnabled();
void showWirelessChargingAnimation(int batteryLevel);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5baf6a0..1d6b744 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -168,6 +168,7 @@
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -594,6 +595,8 @@
private final SceneContainerFlags mSceneContainerFlags;
+ private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor;
+
/**
* Public constructor for CentralSurfaces.
*
@@ -705,7 +708,8 @@
UserTracker userTracker,
Provider<FingerprintManager> fingerprintManager,
ActivityStarter activityStarter,
- SceneContainerFlags sceneContainerFlags
+ SceneContainerFlags sceneContainerFlags,
+ BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor
) {
mContext = context;
mNotificationsController = notificationsController;
@@ -801,6 +805,7 @@
mFingerprintManager = fingerprintManager;
mActivityStarter = activityStarter;
mSceneContainerFlags = sceneContainerFlags;
+ mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -1083,6 +1088,12 @@
mJavaAdapter.alwaysCollectFlow(
mCommunalInteractor.isIdleOnCommunal(),
mIdleOnCommunalConsumer);
+ if (mSceneContainerFlags.isEnabled()) {
+ mJavaAdapter.alwaysCollectFlow(
+ mBrightnessMirrorShowingInteractor.isShowing(),
+ this::setBrightnessMirrorShowing
+ );
+ }
}
/**
@@ -1284,10 +1295,7 @@
mShadeSurface,
mNotificationShadeDepthControllerLazy.get(),
mBrightnessSliderFactory,
- (visible) -> {
- mBrightnessMirrorVisible = visible;
- updateScrimController();
- });
+ this::setBrightnessMirrorShowing);
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragmentLegacy) {
@@ -1346,6 +1354,10 @@
ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f));
}
+ private void setBrightnessMirrorShowing(boolean showing) {
+ mBrightnessMirrorVisible = showing;
+ updateScrimController();
+ }
/**
* When swiping up to dismiss the lock screen, the panel expansion fraction goes from 1f to 0f.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 495b4e1..4c3c7d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -41,6 +41,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -421,9 +422,12 @@
public void updateHeader(NotificationEntry entry) {
ExpandableNotificationRow row = entry.getRow();
float headerVisibleAmount = 1.0f;
- if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild
- || row.showingPulsing()) {
- headerVisibleAmount = mAppearFraction;
+ // To fix the invisible HUN group header issue
+ if (!AsyncGroupHeaderViewInflation.isEnabled()) {
+ if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild
+ || row.showingPulsing()) {
+ headerVisibleAmount = mAppearFraction;
+ }
}
row.setHeaderVisibleAmount(headerVisibleAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 13f76fe..7d920ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -27,6 +27,7 @@
import com.android.systemui.res.R;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.settings.brightness.ToggleSlider;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeViewController;
@@ -38,8 +39,7 @@
/**
* Controls showing and hiding of the brightness mirror.
*/
-public class BrightnessMirrorController
- implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
+public class BrightnessMirrorController implements MirrorController {
private final NotificationShadeWindowView mStatusBarWindow;
private final Consumer<Boolean> mVisibilityCallback;
@@ -71,6 +71,7 @@
updateResources();
}
+ @Override
public void showMirror() {
mBrightnessMirror.setVisibility(View.VISIBLE);
mVisibilityCallback.accept(true);
@@ -78,16 +79,14 @@
mDepthController.setBrightnessMirrorVisible(true);
}
+ @Override
public void hideMirror() {
mVisibilityCallback.accept(false);
mNotificationPanel.setAlpha(255, true /* animate */);
mDepthController.setBrightnessMirrorVisible(false);
}
- /**
- * Set the location and size of the mirror container to match that of the slider in QS
- * @param original the original view in QS
- */
+ @Override
public void setLocationAndSize(View original) {
original.getLocationInWindow(mInt2Cache);
@@ -112,6 +111,7 @@
}
}
+ @Override
public ToggleSlider getToggleSlider() {
return mToggleSliderController;
}
@@ -176,8 +176,4 @@
public void onUiModeChanged() {
reinflate();
}
-
- public interface BrightnessMirrorListener {
- void onBrightnessMirrorReinflated(View brightnessMirror);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index e977014..aea739d 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -20,11 +20,10 @@
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.annotation.BinderThread
-import android.content.Context
-import android.os.Handler
import android.os.SystemProperties
import android.util.Log
import android.view.animation.DecelerateInterpolator
+import com.android.app.tracing.TraceUtils.traceAsync
import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -36,12 +35,13 @@
import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Companion.isVerticalRotation
import com.android.systemui.unfold.dagger.UnfoldBg
import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import com.android.systemui.util.kotlin.race
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
-import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -59,13 +59,13 @@
class FoldLightRevealOverlayAnimation
@Inject
constructor(
- private val context: Context,
- @UnfoldBg private val bgHandler: Handler,
+ @UnfoldBg private val bgDispatcher: CoroutineDispatcher,
private val deviceStateRepository: DeviceStateRepository,
private val powerInteractor: PowerInteractor,
@Background private val applicationScope: CoroutineScope,
private val animationStatusRepository: AnimationStatusRepository,
- private val controllerFactory: FullscreenLightRevealAnimationController.Factory
+ private val controllerFactory: FullscreenLightRevealAnimationController.Factory,
+ private val foldLockSettingAvailabilityProvider: FoldLockSettingAvailabilityProvider
) : FullscreenLightRevealAnimation {
private val revealProgressValueAnimator: ValueAnimator =
@@ -79,7 +79,7 @@
override fun init() {
// This method will be called only on devices where this animation is enabled,
// so normally this thread won't be created
- if (!FoldLockSettingAvailabilityProvider(context.resources).isFoldLockBehaviorAvailable) {
+ if (!foldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable) {
return
}
@@ -91,7 +91,6 @@
)
controller.init()
- val bgDispatcher = bgHandler.asCoroutineDispatcher("@UnfoldBg Handler")
applicationScope.launch(bgDispatcher) {
powerInteractor.screenPowerState.collect {
if (it == ScreenPowerState.SCREEN_ON) {
@@ -109,14 +108,21 @@
if (!areAnimationEnabled.first() || !isFolded) {
return@flow
}
- withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
- readyCallback = CompletableDeferred()
- val onReady = readyCallback?.await()
- readyCallback = null
- controller.addOverlay(ALPHA_OPAQUE, onReady)
- waitForScreenTurnedOn()
- }
- playFoldLightRevealOverlayAnimation()
+ race(
+ {
+ traceAsync(TAG, "prepareAndPlayFoldAnimation()") {
+ withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+ readyCallback = CompletableDeferred()
+ val onReady = readyCallback?.await()
+ readyCallback = null
+ controller.addOverlay(ALPHA_OPAQUE, onReady)
+ waitForScreenTurnedOn()
+ }
+ playFoldLightRevealOverlayAnimation()
+ }
+ },
+ { waitForGoToSleep() }
+ )
}
.catchTimeoutAndLog()
.onCompletion {
@@ -135,9 +141,13 @@
readyCallback?.complete(onOverlayReady) ?: onOverlayReady.run()
}
- private suspend fun waitForScreenTurnedOn() {
- powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
- }
+ private suspend fun waitForScreenTurnedOn() =
+ traceAsync(TAG, "waitForScreenTurnedOn()") {
+ powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+ }
+
+ private suspend fun waitForGoToSleep() =
+ traceAsync(TAG, "waitForGoToSleep()") { powerInteractor.isAsleep.filter { it }.first() }
private suspend fun playFoldLightRevealOverlayAnimation() {
revealProgressValueAnimator.duration = ANIMATION_DURATION
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 9bd0e32..3522850 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.os.SystemProperties
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
import com.android.systemui.CoreStartable
import com.android.systemui.Flags
import com.android.systemui.dagger.qualifiers.Application
@@ -175,6 +176,12 @@
fun provideDisplaySwitchLatencyLogger(): DisplaySwitchLatencyLogger =
DisplaySwitchLatencyLogger()
+ @Provides
+ @Singleton
+ fun provideFoldLockSettingAvailabilityProvider(
+ context: Context
+ ): FoldLockSettingAvailabilityProvider = FoldLockSettingAvailabilityProvider(context.resources)
+
@Module
interface Bindings {
@Binds fun bindRepository(impl: UnfoldTransitionRepositoryImpl): UnfoldTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 9bfc4ce..1568e8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -20,8 +20,12 @@
import static android.app.WallpaperManager.FLAG_SYSTEM;
import static android.app.WallpaperManager.SetWallpaperFlags;
+import static com.android.window.flags.Flags.offloadColorExtraction;
+
+import android.annotation.Nullable;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RecordingCanvas;
@@ -134,6 +138,12 @@
mLongExecutor,
mLock,
new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
+
+ @Override
+ public void onColorsProcessed() {
+ CanvasEngine.this.notifyColorsChanged();
+ }
+
@Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
@@ -183,8 +193,11 @@
@Override
public void onDestroy() {
- getDisplayContext().getSystemService(DisplayManager.class)
- .unregisterDisplayListener(this);
+ Context context = getDisplayContext();
+ if (context != null) {
+ DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ if (displayManager != null) displayManager.unregisterDisplayListener(this);
+ }
mWallpaperLocalColorExtractor.cleanUp();
}
@@ -429,6 +442,12 @@
}
@Override
+ public @Nullable WallpaperColors onComputeColors() {
+ if (!offloadColorExtraction()) return null;
+ return mWallpaperLocalColorExtractor.onComputeColors();
+ }
+
+ @Override
public boolean supportsLocalColorExtraction() {
return true;
}
@@ -465,6 +484,12 @@
}
@Override
+ public void onDimAmountChanged(float dimAmount) {
+ if (!offloadColorExtraction()) return;
+ mWallpaperLocalColorExtractor.onDimAmountChanged(dimAmount);
+ }
+
+ @Override
public void onDisplayAdded(int displayId) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
index e2ec8dc..d37dfb4 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
@@ -17,6 +17,8 @@
package com.android.systemui.wallpapers;
+import static com.android.window.flags.Flags.offloadColorExtraction;
+
import android.app.WallpaperColors;
import android.graphics.Bitmap;
import android.graphics.Rect;
@@ -66,6 +68,12 @@
private final List<RectF> mPendingRegions = new ArrayList<>();
private final Set<RectF> mProcessedRegions = new ArraySet<>();
+ private float mWallpaperDimAmount = 0f;
+ private WallpaperColors mWallpaperColors;
+
+ // By default we assume that colors were loaded from disk and don't need to be recomputed
+ private boolean mRecomputeColors = false;
+
@LongRunning
private final Executor mLongExecutor;
@@ -75,6 +83,12 @@
* Interface to handle the callbacks after the different steps of the color extraction
*/
public interface WallpaperLocalColorExtractorCallback {
+
+ /**
+ * Callback after the wallpaper colors have been computed
+ */
+ void onColorsProcessed();
+
/**
* Callback after the colors of new regions have been extracted
* @param regions the list of new regions that have been processed
@@ -129,7 +143,7 @@
if (displayWidth == mDisplayWidth && displayHeight == mDisplayHeight) return;
mDisplayWidth = displayWidth;
mDisplayHeight = displayHeight;
- processColorsInternal();
+ processLocalColorsInternal();
}
}
@@ -166,7 +180,8 @@
mBitmapHeight = bitmap.getHeight();
mMiniBitmap = createMiniBitmap(bitmap);
mWallpaperLocalColorExtractorCallback.onMiniBitmapUpdated();
- recomputeColors();
+ if (offloadColorExtraction() && mRecomputeColors) recomputeColorsInternal();
+ recomputeLocalColors();
}
}
@@ -184,16 +199,66 @@
if (mPages == pages) return;
mPages = pages;
if (mMiniBitmap != null && !mMiniBitmap.isRecycled()) {
- recomputeColors();
+ recomputeLocalColors();
}
}
}
- // helper to recompute colors, to be called in synchronized methods
- private void recomputeColors() {
+ /**
+ * Should be called when the dim amount of the wallpaper changes, to recompute the colors
+ */
+ public void onDimAmountChanged(float dimAmount) {
+ mLongExecutor.execute(() -> onDimAmountChangedSynchronized(dimAmount));
+ }
+
+ private void onDimAmountChangedSynchronized(float dimAmount) {
+ synchronized (mLock) {
+ if (mWallpaperDimAmount == dimAmount) return;
+ mWallpaperDimAmount = dimAmount;
+ mRecomputeColors = true;
+ recomputeColorsInternal();
+ }
+ }
+
+ /**
+ * To be called by {@link ImageWallpaper.CanvasEngine#onComputeColors}. This will either
+ * return the current wallpaper colors, or if the bitmap is not yet loaded, return null and call
+ * {@link WallpaperLocalColorExtractorCallback#onColorsProcessed()} when the colors are ready.
+ */
+ public WallpaperColors onComputeColors() {
+ mLongExecutor.execute(this::onComputeColorsSynchronized);
+ return mWallpaperColors;
+ }
+
+ private void onComputeColorsSynchronized() {
+ synchronized (mLock) {
+ if (mRecomputeColors) return;
+ mRecomputeColors = true;
+ recomputeColorsInternal();
+ }
+ }
+
+ /**
+ * helper to recompute main colors, to be called in synchronized methods
+ */
+ private void recomputeColorsInternal() {
+ if (mMiniBitmap == null) return;
+ mWallpaperColors = getWallpaperColors(mMiniBitmap, mWallpaperDimAmount);
+ mWallpaperLocalColorExtractorCallback.onColorsProcessed();
+ }
+
+ @VisibleForTesting
+ WallpaperColors getWallpaperColors(@NonNull Bitmap bitmap, float dimAmount) {
+ return WallpaperColors.fromBitmap(bitmap, dimAmount);
+ }
+
+ /**
+ * helper to recompute local colors, to be called in synchronized methods
+ */
+ private void recomputeLocalColors() {
mPendingRegions.addAll(mProcessedRegions);
mProcessedRegions.clear();
- processColorsInternal();
+ processLocalColorsInternal();
}
/**
@@ -216,7 +281,7 @@
if (!wasActive && isActive()) {
mWallpaperLocalColorExtractorCallback.onActivated();
}
- processColorsInternal();
+ processLocalColorsInternal();
}
}
@@ -353,7 +418,7 @@
* then notify the callback with the resulting colors for these regions
* This method should only be called synchronously
*/
- private void processColorsInternal() {
+ private void processLocalColorsInternal() {
/*
* if the miniBitmap is not yet loaded, that means the onBitmapChanged has not yet been
* called, and thus the wallpaper is not yet loaded. In that case, exit, the function
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 6a35340..ca55dd8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -330,7 +330,8 @@
TransitionStep(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
- value = 0.4f
+ value = 0.4f,
+ transitionState = TransitionState.RUNNING,
)
yield()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index b0f0363..1576457 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -43,10 +43,10 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItemType;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItemType;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
index 95d5597..d16db65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapterTest.java
@@ -27,7 +27,7 @@
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItem;
+import com.android.systemui.bluetooth.qsdialog.DeviceItem;
import org.junit.Before;
import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 1acb203..238a76e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -33,6 +33,7 @@
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.settingslib.Utils
+import com.android.systemui.Flags.FLAG_CONSTRAINT_BP
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
@@ -351,6 +352,7 @@
@Test
fun updatesOverlayViewParams_onDisplayRotationChange_xAlignedSensor() {
testScope.runTest {
+ mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
setupTestConfiguration(
DeviceConfig.X_ALIGNED,
rotation = DisplayRotation.ROTATION_0,
@@ -392,6 +394,7 @@
@Test
fun updatesOverlayViewParams_onDisplayRotationChange_yAlignedSensor() {
testScope.runTest {
+ mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
setupTestConfiguration(
DeviceConfig.Y_ALIGNED,
rotation = DisplayRotation.ROTATION_0,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
index 036d3c8..4949716 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnInteractorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt
index 3119284..85e2a8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothAutoOnRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
index 479e62d..a8f82ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothStateInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 17b6127..12dfe97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
-import android.content.Context
import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -121,23 +120,18 @@
sysuiDialogFactory
)
- whenever(
- sysuiDialogFactory.create(
- any(SystemUIDialog.Delegate::class.java)
- )
+ whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java))).thenAnswer {
+ SystemUIDialog(
+ mContext,
+ 0,
+ SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ dialogManager,
+ sysuiState,
+ fakeBroadcastDispatcher,
+ dialogTransitionAnimator,
+ it.getArgument(0)
)
- .thenAnswer {
- SystemUIDialog(
- mContext,
- 0,
- SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
- dialogManager,
- sysuiState,
- fakeBroadcastDispatcher,
- dialogTransitionAnimator,
- it.getArgument(0)
- )
- }
+ }
icon = Pair(drawable, DEVICE_NAME)
deviceItem =
@@ -163,6 +157,7 @@
assertThat(recyclerView.visibility).isEqualTo(VISIBLE)
assertThat(recyclerView.adapter).isNotNull()
assertThat(recyclerView.layoutManager is LinearLayoutManager).isTrue()
+ dialog.dismiss()
}
@Test
@@ -184,6 +179,7 @@
assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY)
assertThat(adapter.getItem(0).iconWithDescription).isEqualTo(icon)
+ dialog.dismiss()
}
}
@@ -259,6 +255,7 @@
assertThat(pairNewButton.visibility).isEqualTo(VISIBLE)
assertThat(adapter.itemCount).isEqualTo(1)
assertThat(scrollViewContent.layoutParams.height).isEqualTo(WRAP_CONTENT)
+ dialog.dismiss()
}
}
@@ -283,6 +280,7 @@
dialog.show()
assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
.isEqualTo(cachedHeight)
+ dialog.dismiss()
}
}
@@ -306,6 +304,7 @@
dialog.show()
assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
.isGreaterThan(MATCH_PARENT)
+ dialog.dismiss()
}
}
@@ -331,6 +330,7 @@
dialog.requireViewById<View>(R.id.bluetooth_auto_on_toggle_layout).visibility
)
.isEqualTo(GONE)
+ dialog.dismiss()
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt
index da8f60a..4aa6209 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index c8a2aa6..6d99c5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.testing.AndroidTestingRunner
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index a8cd8c8..28cbcb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothDevice
import android.content.pm.PackageInfo
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index ddf0b9a..eb735cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.tiles.dialog.bluetooth
+package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index 45d20dc..8e5ddc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -27,6 +27,7 @@
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -202,6 +203,36 @@
}
@Test
+ public void testPassThroughEnterKeyEvent() {
+ KeyEvent enterDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER,
+ 0, 0, 0, 0, 0, 0, 0, "");
+ KeyEvent enterUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0,
+ 0, 0, 0, 0, 0, 0, "");
+
+ mFalsingCollector.onKeyEvent(enterDown);
+ verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+
+ mFalsingCollector.onKeyEvent(enterUp);
+ verify(mFalsingDataProvider, times(1)).onKeyEvent(enterUp);
+ }
+
+ @Test
+ public void testAvoidAKeyEvent() {
+ // Arbitrarily chose the "A" key, as it is not currently allowlisted. If this key is
+ // allowlisted in the future, please choose another key that will not be collected.
+ KeyEvent aKeyDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A,
+ 0, 0, 0, 0, 0, 0, 0, "");
+ KeyEvent aKeyUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0,
+ 0, 0, 0, 0, 0, 0, "");
+
+ mFalsingCollector.onKeyEvent(aKeyDown);
+ verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+
+ mFalsingCollector.onKeyEvent(aKeyUp);
+ verify(mFalsingDataProvider, never()).onKeyEvent(any(KeyEvent.class));
+ }
+
+ @Test
public void testPassThroughGesture() {
MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 0353f87..057b0a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -27,6 +27,7 @@
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.testing.AndroidTestingRunner;
import android.util.DisplayMetrics;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -282,6 +283,22 @@
}
@Test
+ public void test_isFromKeyboard_disallowedKey_false() {
+ KeyEvent eventDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0,
+ 0, 0, 0, 0, 0, 0, "");
+ KeyEvent eventUp = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+ 0, 0, 0, 0, 0, "");
+
+ //events have not come in yet
+ assertThat(mDataProvider.isFromKeyboard()).isFalse();
+
+ mDataProvider.onKeyEvent(eventDown);
+ mDataProvider.onKeyEvent(eventUp);
+ assertThat(mDataProvider.isFromKeyboard()).isTrue();
+ mDataProvider.onSessionEnd();
+ }
+
+ @Test
public void test_IsFromTrackpad() {
MotionEvent motionEventOrigin = appendTrackpadDownEvent(0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
new file mode 100644
index 0000000..ad7afa3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedInputEventBufferTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.classifier;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.testing.AndroidTestingRunner;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class TimeLimitedInputEventBufferTest extends SysuiTestCase {
+
+ private static final long MAX_AGE_MS = 100;
+
+ private TimeLimitedInputEventBuffer<InputEvent> mBuffer;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mBuffer = new TimeLimitedInputEventBuffer<>(MAX_AGE_MS);
+ }
+
+ @After
+ public void tearDown() {
+ for (InputEvent inputEvent : mBuffer) {
+ inputEvent.recycle();
+ }
+ mBuffer.clear();
+ }
+
+ @Test
+ public void testMotionEventAllEventsRetained() {
+ MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ MotionEvent eventC = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ MotionEvent eventD = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 0, 0, 0);
+
+ mBuffer.add(eventA);
+ mBuffer.add(eventB);
+ mBuffer.add(eventC);
+ mBuffer.add(eventD);
+
+ assertThat(mBuffer.size(), is(4));
+ }
+
+ @Test
+ public void testMotionEventOlderEventsRemoved() {
+ MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+ MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ MotionEvent eventC = MotionEvent.obtain(
+ 0, MAX_AGE_MS + 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ MotionEvent eventD = MotionEvent.obtain(
+ 0, MAX_AGE_MS + 2, MotionEvent.ACTION_UP, 0, 0, 0);
+
+ mBuffer.add(eventA);
+ mBuffer.add(eventB);
+ assertThat(mBuffer.size(), is(2));
+
+ mBuffer.add(eventC);
+ mBuffer.add(eventD);
+ assertThat(mBuffer.size(), is(2));
+
+ assertThat(mBuffer.get(0), is(eventC));
+ assertThat(mBuffer.get(1), is(eventD));
+ }
+
+ @Test
+ public void testKeyEventAllEventsRetained() {
+ KeyEvent eventA = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+ 0, 0, 0, 0, 0, "");
+ KeyEvent eventB = KeyEvent.obtain(0, 3, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0, 0, 0, 0,
+ 0, 0, 0, "");
+
+ mBuffer.add(eventA);
+ mBuffer.add(eventB);
+
+ assertThat(mBuffer.size(), is(2));
+ }
+
+ @Test
+ public void testKeyEventOlderEventsRemoved() {
+ KeyEvent eventA = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
+ 0, 0, 0, 0, 0, "");
+ KeyEvent eventB = KeyEvent.obtain(0, 1, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A, 0, 0, 0, 0,
+ 0, 0, 0, "");
+ KeyEvent eventC = KeyEvent.obtain(0, MAX_AGE_MS + 1, MotionEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_A, 0, 0, 0, 0, 0, 0, 0, "");
+ KeyEvent eventD = KeyEvent.obtain(0, MAX_AGE_MS + 2, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A,
+ 0, 0, 0, 0, 0, 0, 0, "");
+
+ mBuffer.add(eventA);
+ mBuffer.add(eventB);
+ assertThat(mBuffer.size(), is(2));
+
+ mBuffer.add(eventC);
+ mBuffer.add(eventD);
+ assertThat(mBuffer.size(), is(2));
+
+ assertThat(mBuffer.get(0), is(eventC));
+ assertThat(mBuffer.get(1), is(eventD));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java
deleted file mode 100644
index 901196f..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/TimeLimitedMotionEventBufferTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.classifier;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
-
-import android.testing.AndroidTestingRunner;
-import android.view.MotionEvent;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class TimeLimitedMotionEventBufferTest extends SysuiTestCase {
-
- private static final long MAX_AGE_MS = 100;
-
- private TimeLimitedMotionEventBuffer mBuffer;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mBuffer = new TimeLimitedMotionEventBuffer(MAX_AGE_MS);
- }
-
- @After
- public void tearDown() {
- for (MotionEvent motionEvent : mBuffer) {
- motionEvent.recycle();
- }
- mBuffer.clear();
- }
-
- @Test
- public void testAllEventsRetained() {
- MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
- MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
- MotionEvent eventC = MotionEvent.obtain(0, 2, MotionEvent.ACTION_MOVE, 0, 0, 0);
- MotionEvent eventD = MotionEvent.obtain(0, 3, MotionEvent.ACTION_UP, 0, 0, 0);
-
- mBuffer.add(eventA);
- mBuffer.add(eventB);
- mBuffer.add(eventC);
- mBuffer.add(eventD);
-
- assertThat(mBuffer.size(), is(4));
- }
-
- @Test
- public void testOlderEventsRemoved() {
- MotionEvent eventA = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
- MotionEvent eventB = MotionEvent.obtain(0, 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
- MotionEvent eventC = MotionEvent.obtain(
- 0, MAX_AGE_MS + 1, MotionEvent.ACTION_MOVE, 0, 0, 0);
- MotionEvent eventD = MotionEvent.obtain(
- 0, MAX_AGE_MS + 2, MotionEvent.ACTION_UP, 0, 0, 0);
-
- mBuffer.add(eventA);
- mBuffer.add(eventB);
- assertThat(mBuffer.size(), is(2));
-
- mBuffer.add(eventC);
- mBuffer.add(eventD);
- assertThat(mBuffer.size(), is(2));
-
- assertThat(mBuffer.get(0), is(eventC));
- assertThat(mBuffer.get(1), is(eventD));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 2ca5aef..c47f0bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -23,6 +23,7 @@
import android.view.View.VISIBLE
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -70,7 +71,7 @@
Utils.getStatusBarHeaderHeightKeyguard(context)
private val LARGE_CLOCK_TOP_WITHOUT_SMARTSPACE =
- context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+ SystemBarUtils.getStatusBarHeight(context) +
context.resources.getDimensionPixelSize(
com.android.systemui.customization.R.dimen.small_clock_padding_top
) +
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 695d3b2..ca403e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -47,6 +48,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.media.LocalMediaManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastSender;
@@ -127,6 +129,12 @@
mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
mKeyguardManager, mFlags, mUserTracker);
+ // Using a fake package will cause routing operations to fail, so we intercept
+ // scanning-related operations.
+ mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
+ doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
+ doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
+
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 44798ea..8b79fa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -257,6 +257,7 @@
userId = userId,
colorBackground = 0,
isForegroundTask = isForegroundTask,
+ userType = RecentTask.UserType.STANDARD,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
similarity index 78%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
index 9b346d0..fa1c8f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/IconLoaderLibAppIconLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
@@ -20,15 +20,10 @@
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
-import android.graphics.drawable.Drawable
import androidx.test.filters.SmallTest
-import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.FastBitmapDrawable
-import com.android.launcher3.icons.IconFactory
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.system.PackageManagerWrapper
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -40,20 +35,17 @@
@SmallTest
@RunWith(JUnit4::class)
-class IconLoaderLibAppIconLoaderTest : SysuiTestCase() {
+class BasicPackageManagerAppIconLoaderTest : SysuiTestCase() {
- private val iconFactory: IconFactory = mock()
private val packageManagerWrapper: PackageManagerWrapper = mock()
private val packageManager: PackageManager = mock()
private val dispatcher = Dispatchers.Unconfined
private val appIconLoader =
- IconLoaderLibAppIconLoader(
+ BasicPackageManagerAppIconLoader(
backgroundDispatcher = dispatcher,
- context = context,
packageManagerWrapper = packageManagerWrapper,
packageManager = packageManager,
- iconFactoryProvider = { iconFactory }
)
@Test
@@ -70,12 +62,7 @@
private fun givenIcon(component: ComponentName, userId: Int, icon: FastBitmapDrawable) {
val activityInfo = mock<ActivityInfo>()
whenever(packageManagerWrapper.getActivityInfo(component, userId)).thenReturn(activityInfo)
- val rawIcon = mock<Drawable>()
- whenever(activityInfo.loadIcon(packageManager)).thenReturn(rawIcon)
-
- val bitmapInfo = mock<BitmapInfo>()
- whenever(iconFactory.createBadgedIconBitmap(eq(rawIcon), any())).thenReturn(bitmapInfo)
- whenever(bitmapInfo.newIcon(context)).thenReturn(icon)
+ whenever(activityInfo.loadIcon(packageManager)).thenReturn(icon)
}
private fun createIcon(): FastBitmapDrawable =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index b593def..dd62112 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -1,9 +1,15 @@
package com.android.systemui.mediaprojection.appselector.data
import android.app.ActivityManager.RecentTaskInfo
+import android.content.pm.UserInfo
+import android.os.UserManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.CLONED
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.PRIVATE
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.STANDARD
+import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.WORK
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -17,6 +23,7 @@
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -25,12 +32,16 @@
private val dispatcher = Dispatchers.Unconfined
private val recentTasks: RecentTasks = mock()
private val userTracker: UserTracker = mock()
+ private val userManager: UserManager = mock {
+ whenever(getUserInfo(anyInt())).thenReturn(mock())
+ }
private val recentTaskListProvider =
ShellRecentTaskListProvider(
dispatcher,
Runnable::run,
Optional.of(recentTasks),
- userTracker
+ userTracker,
+ userManager,
)
@Test
@@ -147,6 +158,22 @@
.inOrder()
}
+ @Test
+ fun loadRecentTasks_assignsCorrectUserType() {
+ givenRecentTasks(
+ createSingleTask(taskId = 1, userId = 10, userType = STANDARD),
+ createSingleTask(taskId = 2, userId = 20, userType = WORK),
+ createSingleTask(taskId = 3, userId = 30, userType = CLONED),
+ createSingleTask(taskId = 4, userId = 40, userType = PRIVATE),
+ )
+
+ val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+ assertThat(result.map { it.userType })
+ .containsExactly(STANDARD, WORK, CLONED, PRIVATE)
+ .inOrder()
+ }
+
@Suppress("UNCHECKED_CAST")
private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
@@ -155,7 +182,10 @@
}
}
- private fun createRecentTask(taskId: Int): RecentTask =
+ private fun createRecentTask(
+ taskId: Int,
+ userType: RecentTask.UserType = STANDARD
+ ): RecentTask =
RecentTask(
taskId = taskId,
displayId = 0,
@@ -164,25 +194,42 @@
baseIntentComponent = null,
colorBackground = null,
isForegroundTask = false,
+ userType = userType,
)
- private fun createSingleTask(taskId: Int, isVisible: Boolean = false): GroupedRecentTaskInfo =
- GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, isVisible))
+ private fun createSingleTask(
+ taskId: Int,
+ userId: Int = 0,
+ isVisible: Boolean = false,
+ userType: RecentTask.UserType = STANDARD,
+ ): GroupedRecentTaskInfo {
+ val userInfo =
+ mock<UserInfo> {
+ whenever(isCloneProfile).thenReturn(userType == CLONED)
+ whenever(isManagedProfile).thenReturn(userType == WORK)
+ whenever(isPrivateProfile).thenReturn(userType == PRIVATE)
+ }
+ whenever(userManager.getUserInfo(userId)).thenReturn(userInfo)
+ return GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, userId, isVisible))
+ }
private fun createTaskPair(
taskId1: Int,
+ userId1: Int = 0,
taskId2: Int,
+ userId2: Int = 0,
isVisible: Boolean = false
): GroupedRecentTaskInfo =
GroupedRecentTaskInfo.forSplitTasks(
- createTaskInfo(taskId1, isVisible),
- createTaskInfo(taskId2, isVisible),
+ createTaskInfo(taskId1, userId1, isVisible),
+ createTaskInfo(taskId2, userId2, isVisible),
null
)
- private fun createTaskInfo(taskId: Int, isVisible: Boolean = false) =
+ private fun createTaskInfo(taskId: Int, userId: Int, isVisible: Boolean = false) =
RecentTaskInfo().apply {
this.taskId = taskId
this.isVisible = isVisible
+ this.userId = userId
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
index ac41073..a84008b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewControllerTest.kt
@@ -56,7 +56,8 @@
topActivityComponent = null,
baseIntentComponent = null,
colorBackground = null,
- isForegroundTask = false
+ isForegroundTask = false,
+ userType = RecentTask.UserType.STANDARD,
)
private val taskView =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 82ee99a..830f08a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -23,7 +23,7 @@
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel
import com.android.systemui.statusbar.policy.BluetoothController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 9104f8e..6846c72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -88,6 +88,7 @@
private lateinit var dialog: SystemUIDialog
private lateinit var factory: SystemUIDialog.Factory
private lateinit var latch: CountDownLatch
+ private var issueRecordingState = IssueRecordingState()
@Before
fun setup() {
@@ -128,6 +129,7 @@
mediaProjectionMetricsLogger,
userFileManager,
screenCaptureDisabledDialogDelegate,
+ issueRecordingState,
) {
latch.countDown()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
new file mode 100644
index 0000000..5e7d8fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.Window
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyBlocking
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActionExecutorTest : SysuiTestCase() {
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = StandardTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
+
+ private val intentExecutor = mock<ActionIntentExecutor>()
+ private val window = mock<Window>()
+ private val view = mock<View>()
+ private val onDismiss = mock<(() -> Unit)>()
+ private val pendingIntent = mock<PendingIntent>()
+
+ private lateinit var actionExecutor: ActionExecutor
+
+ @Test
+ fun startSharedTransition_callsLaunchIntent() = runTest {
+ actionExecutor = createActionExecutor()
+
+ actionExecutor.startSharedTransition(Intent(Intent.ACTION_EDIT), UserHandle.CURRENT, true)
+ scheduler.advanceUntilIdle()
+
+ val intentCaptor = argumentCaptor<Intent>()
+ verifyBlocking(intentExecutor) {
+ launchIntent(capture(intentCaptor), eq(UserHandle.CURRENT), eq(true), any(), any())
+ }
+ assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT)
+ }
+
+ @Test
+ fun sendPendingIntent_dismisses() = runTest {
+ actionExecutor = createActionExecutor()
+
+ actionExecutor.sendPendingIntent(pendingIntent)
+
+ verify(pendingIntent).send(any(Bundle::class.java))
+ verify(onDismiss).invoke()
+ }
+
+ private fun createActionExecutor(): ActionExecutor {
+ return ActionExecutor(intentExecutor, testScope, window, view, onDismiss)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
index 0c32470..5e53fe1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
@@ -64,7 +64,7 @@
val intent = Intent(Intent.ACTION_EDIT).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
val userHandle = myUserHandle()
- actionIntentExecutor.launchIntent(intent, null, userHandle, false)
+ actionIntentExecutor.launchIntent(intent, userHandle, false, null, null)
scheduler.advanceUntilIdle()
verify(activityManagerWrapper)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
index 4a5cf57..bde821b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -16,12 +16,11 @@
package com.android.systemui.screenshot
-import android.app.ActivityOptions
-import android.app.ExitTransitionCoordinator
import android.app.Notification
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
+import android.os.Process
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.view.accessibility.AccessibilityManager
@@ -36,11 +35,7 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
-import kotlin.test.Ignore
import kotlin.test.Test
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertNotNull
import org.junit.Before
@@ -49,32 +44,18 @@
import org.mockito.kotlin.any
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyBlocking
import org.mockito.kotlin.whenever
@RunWith(AndroidTestingRunner::class)
@SmallTest
class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
- private val scheduler = TestCoroutineScheduler()
- private val mainDispatcher = StandardTestDispatcher(scheduler)
- private val testScope = TestScope(mainDispatcher)
-
- private val actionIntentExecutor = mock<ActionIntentExecutor>()
+ private val actionExecutor = mock<ActionExecutor>()
private val accessibilityManager = mock<AccessibilityManager>()
private val uiEventLogger = mock<UiEventLogger>()
private val smartActionsProvider = mock<SmartActionsProvider>()
- private val transition = mock<android.util.Pair<ActivityOptions, ExitTransitionCoordinator>>()
- private val requestDismissal = mock<() -> Unit>()
private val request = ScreenshotData.forTesting()
- private val invalidResult = ScreenshotController.SavedImageData()
- private val validResult =
- ScreenshotController.SavedImageData().apply {
- uri = Uri.EMPTY
- owner = UserHandle.OWNER
- subject = "Test"
- imageTime = 0
- }
+ private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0)
private lateinit var viewModel: ScreenshotViewModel
private lateinit var actionsProvider: ScreenshotActionsProvider
@@ -91,7 +72,7 @@
assertNotNull(viewModel.previewAction.value)
viewModel.previewAction.value!!.invoke()
- verifyNoMoreInteractions(actionIntentExecutor)
+ verifyNoMoreInteractions(actionExecutor)
}
@Test
@@ -105,39 +86,24 @@
assertThat(secondAction.onClicked).isNotNull()
firstAction.onClicked!!.invoke()
secondAction.onClicked!!.invoke()
- verifyNoMoreInteractions(actionIntentExecutor)
+ verifyNoMoreInteractions(actionExecutor)
}
@Test
- fun actionAccessed_withInvalidResult_doesNothing() {
- actionsProvider = createActionsProvider()
-
- actionsProvider.setCompletedScreenshot(invalidResult)
- viewModel.previewAction.value!!.invoke()
- viewModel.actions.value[1].onClicked!!.invoke()
-
- verifyNoMoreInteractions(actionIntentExecutor)
- }
-
- @Test
- @Ignore("b/332526567")
fun actionAccessed_withResult_launchesIntent() = runTest {
actionsProvider = createActionsProvider()
actionsProvider.setCompletedScreenshot(validResult)
viewModel.actions.value[0].onClicked!!.invoke()
- scheduler.advanceUntilIdle()
verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED), eq(0), eq(""))
val intentCaptor = argumentCaptor<Intent>()
- verifyBlocking(actionIntentExecutor) {
- launchIntent(capture(intentCaptor), eq(transition), eq(UserHandle.CURRENT), eq(true))
- }
+ verify(actionExecutor)
+ .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(true))
assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT)
}
@Test
- @Ignore("b/332526567")
fun actionAccessed_whilePending_launchesMostRecentAction() = runTest {
actionsProvider = createActionsProvider()
@@ -145,13 +111,11 @@
viewModel.previewAction.value!!.invoke()
viewModel.actions.value[1].onClicked!!.invoke()
actionsProvider.setCompletedScreenshot(validResult)
- scheduler.advanceUntilIdle()
verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED), eq(0), eq(""))
val intentCaptor = argumentCaptor<Intent>()
- verifyBlocking(actionIntentExecutor) {
- launchIntent(capture(intentCaptor), eq(transition), eq(UserHandle.CURRENT), eq(false))
- }
+ verify(actionExecutor)
+ .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(false))
assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER)
}
@@ -172,7 +136,7 @@
(it.getArgument(2) as ((Notification.Action) -> Unit)).invoke(quickShare)
}
whenever(smartActionsProvider.wrapIntent(any(), any(), any(), any())).thenAnswer {
- it.getArgument(0)
+ (it.getArgument(0) as Notification.Action).actionIntent
}
actionsProvider = createActionsProvider()
@@ -190,14 +154,11 @@
return DefaultScreenshotActionsProvider(
context,
viewModel,
- actionIntentExecutor,
smartActionsProvider,
uiEventLogger,
- testScope,
request,
"testid",
- { transition },
- requestDismissal,
+ actionExecutor
)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
new file mode 100644
index 0000000..d44e26c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.ui.viewmodel
+
+import android.view.accessibility.AccessibilityManager
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.mock
+
+@SmallTest
+class ScreenshotViewModelTest {
+ private val accessibilityManager: AccessibilityManager = mock(AccessibilityManager::class.java)
+ private val appearance = ActionButtonAppearance(null, "Label", "Description")
+ private val onclick = {}
+
+ @Test
+ fun testAddAction() {
+ val viewModel = ScreenshotViewModel(accessibilityManager)
+
+ assertThat(viewModel.actions.value).isEmpty()
+
+ viewModel.addAction(appearance, onclick)
+
+ assertThat(viewModel.actions.value).hasSize(1)
+
+ val added = viewModel.actions.value[0]
+ assertThat(added.appearance).isEqualTo(appearance)
+ assertThat(added.onClicked).isEqualTo(onclick)
+ }
+
+ @Test
+ fun testRemoveAction() {
+ val viewModel = ScreenshotViewModel(accessibilityManager)
+ val firstId = viewModel.addAction(ActionButtonAppearance(null, "", ""), {})
+ val secondId = viewModel.addAction(appearance, onclick)
+
+ assertThat(viewModel.actions.value).hasSize(2)
+ assertThat(firstId).isNotEqualTo(secondId)
+
+ viewModel.removeAction(firstId)
+
+ assertThat(viewModel.actions.value).hasSize(1)
+
+ val remaining = viewModel.actions.value[0]
+ assertThat(remaining.appearance).isEqualTo(appearance)
+ assertThat(remaining.onClicked).isEqualTo(onclick)
+ }
+
+ @Test
+ fun testUpdateActionAppearance() {
+ val viewModel = ScreenshotViewModel(accessibilityManager)
+ val id = viewModel.addAction(appearance, onclick)
+ val otherAppearance = ActionButtonAppearance(null, "Other", "Other")
+
+ viewModel.updateActionAppearance(id, otherAppearance)
+
+ assertThat(viewModel.actions.value).hasSize(1)
+ val updated = viewModel.actions.value[0]
+ assertThat(updated.appearance).isEqualTo(otherAppearance)
+ assertThat(updated.onClicked).isEqualTo(onclick)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 25ba09a..6a22d86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -84,7 +84,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(mirrorController.toggleSlider).thenReturn(mirror)
+ whenever(mirrorController.getToggleSlider()).thenReturn(mirror)
whenever(motionEvent.copy()).thenReturn(motionEvent)
whenever(vibratorHelper.getPrimitiveDurations(anyInt())).thenReturn(intArrayOf(0))
@@ -129,7 +129,7 @@
@Test
fun testNullMirrorNotTrackingTouch() {
- whenever(mirrorController.toggleSlider).thenReturn(null)
+ whenever(mirrorController.getToggleSlider()).thenReturn(null)
mController.setMirrorControllerAndMirror(mirrorController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 02f2e16..cf7c6f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -20,6 +20,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING;
import static com.google.common.truth.Truth.assertThat;
@@ -436,6 +437,10 @@
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) != 0).isTrue();
+ assertThat(
+ (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_TRACING)
+ != 0)
+ .isTrue();
}
@Test
@@ -444,6 +449,10 @@
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue();
+ assertThat(
+ (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_TRACING)
+ == 0)
+ .isTrue();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index dfbb699..2c0a15d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -69,7 +69,6 @@
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
@@ -87,6 +86,8 @@
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import kotlin.test.assertEquals
+import java.util.Optional
import org.mockito.Mockito.`when` as whenever
@OptIn(ExperimentalCoroutinesApi::class)
@@ -138,6 +139,7 @@
private val notificationLaunchAnimationInteractor =
NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)
+ private lateinit var falsingCollector: FalsingCollectorFake
private lateinit var fakeClock: FakeSystemClock
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -170,11 +172,12 @@
mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES)
testScope = TestScope()
+ falsingCollector = FalsingCollectorFake()
fakeClock = FakeSystemClock()
underTest =
NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
- FalsingCollectorFake(),
+ falsingCollector,
sysuiStatusBarStateController,
dockManager,
notificationShadeDepthController,
@@ -566,6 +569,13 @@
verify(sysUIKeyEventHandler).interceptMediaKey(keyEvent)
}
+ @Test
+ fun forwardsCollectKeyEvent() {
+ val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A)
+ interactionEventHandler.collectKeyEvent(keyEvent)
+ assertEquals(keyEvent, falsingCollector.lastKeyEvent)
+ }
+
companion object {
private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index cac4a8d..6bda4d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -299,8 +299,6 @@
@Test
public void testSetFooterLabelVisible() {
mView.setFooterLabelVisible(true);
- assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
- assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.GONE);
assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
.isEqualTo(View.VISIBLE);
}
@@ -308,8 +306,6 @@
@Test
public void testSetFooterLabelInvisible() {
mView.setFooterLabelVisible(false);
- assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
.isEqualTo(View.GONE);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 620d972..158f38d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -66,7 +66,7 @@
val underTest = kosmos.footerViewModel
@Test
- fun testMessageVisible_whenFilteredNotifications() =
+ fun messageVisible_whenFilteredNotifications() =
testScope.runTest {
val visible by collectLastValue(underTest.message.isVisible)
@@ -76,7 +76,7 @@
}
@Test
- fun testMessageVisible_whenNoFilteredNotifications() =
+ fun messageVisible_whenNoFilteredNotifications() =
testScope.runTest {
val visible by collectLastValue(underTest.message.isVisible)
@@ -86,7 +86,7 @@
}
@Test
- fun testClearAllButtonVisible_whenHasClearableNotifs() =
+ fun clearAllButtonVisible_whenHasClearableNotifs() =
testScope.runTest {
val visible by collectLastValue(underTest.clearAllButton.isVisible)
@@ -104,7 +104,7 @@
}
@Test
- fun testClearAllButtonVisible_whenHasNoClearableNotifs() =
+ fun clearAllButtonVisible_whenHasNoClearableNotifs() =
testScope.runTest {
val visible by collectLastValue(underTest.clearAllButton.isVisible)
@@ -122,7 +122,26 @@
}
@Test
- fun testClearAllButtonAnimating_whenShadeExpandedAndTouchable() =
+ fun clearAllButtonVisible_whenMessageVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.clearAllButton.isVisible)
+
+ activeNotificationListRepository.notifStats.value =
+ NotifStats(
+ numActiveNotifs = 2,
+ hasNonClearableAlertingNotifs = false,
+ hasClearableAlertingNotifs = true,
+ hasNonClearableSilentNotifs = false,
+ hasClearableSilentNotifs = true,
+ )
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
+ runCurrent()
+
+ assertThat(visible?.value).isFalse()
+ }
+
+ @Test
+ fun clearAllButtonAnimating_whenShadeExpandedAndTouchable() =
testScope.runTest {
val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
@@ -156,7 +175,7 @@
}
@Test
- fun testClearAllButtonAnimating_whenShadeNotExpanded() =
+ fun clearAllButtonAnimating_whenShadeNotExpanded() =
testScope.runTest {
val visible by collectLastValue(underTest.clearAllButton.isVisible)
runCurrent()
@@ -190,7 +209,7 @@
}
@Test
- fun testManageButton_whenHistoryDisabled() =
+ fun manageButton_whenHistoryDisabled() =
testScope.runTest {
val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
runCurrent()
@@ -203,7 +222,7 @@
}
@Test
- fun testHistoryButton_whenHistoryEnabled() =
+ fun historyButton_whenHistoryEnabled() =
testScope.runTest {
val buttonLabel by collectLastValue(underTest.manageOrHistoryButton.labelId)
runCurrent()
@@ -214,4 +233,24 @@
// THEN label is "History"
assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_history_text)
}
+
+ @Test
+ fun manageButtonVisible_whenMessageVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.manageOrHistoryButton.isVisible)
+
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = true
+
+ assertThat(visible?.value).isFalse()
+ }
+
+ @Test
+ fun manageButtonVisible_whenMessageNotVisible() =
+ testScope.runTest {
+ val visible by collectLastValue(underTest.manageOrHistoryButton.isVisible)
+
+ activeNotificationListRepository.hasFilteredOutSeenNotifications.value = false
+
+ assertThat(visible?.value).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
index 33a838e..4b0b4b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt
@@ -25,6 +25,7 @@
import android.util.StatsEvent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.assertLogsWtf
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -133,8 +134,10 @@
val pipeline: NotifPipeline = mock()
whenever(pipeline.allNotifs).thenThrow(RuntimeException("Something broke!"))
val logger = NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor)
- assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
- .isEqualTo(StatsManager.PULL_SKIP)
+ assertLogsWtf {
+ assertThat(logger.onPullAtom(SysUiStatsLog.NOTIFICATION_MEMORY_USE, mutableListOf()))
+ .isEqualTo(StatsManager.PULL_SKIP)
+ }
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index ed29665..2f153d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -25,6 +25,8 @@
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -119,9 +121,9 @@
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.NotificationPanelView;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -331,7 +333,10 @@
private final DumpManager mDumpManager = new DumpManager();
private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
- private final SceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
+ private final FakeSceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
+
+ private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
+ mKosmos.getBrightnessMirrorShowingInteractor();
@Before
public void setup() throws Exception {
@@ -553,7 +558,8 @@
mUserTracker,
() -> mFingerprintManager,
mActivityStarter,
- mSceneContainerFlags
+ mSceneContainerFlags,
+ mBrightnessMirrorShowingInteractor
);
mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
mCentralSurfaces.initShadeVisibilityListener();
@@ -1084,6 +1090,34 @@
verify(mStatusBarWindowController).refreshStatusBarHeight();
}
+ @Test
+ public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() {
+ mSceneContainerFlags.setEnabled(true);
+ mCentralSurfaces.registerCallbacks();
+
+ mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
+ mTestScope.getTestScheduler().runCurrent();
+ verify(mScrimController).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+
+ mBrightnessMirrorShowingInteractor.setMirrorShowing(false);
+ mTestScope.getTestScheduler().runCurrent();
+ ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class);
+ // The default is to call the one with the callback argument
+ verify(mScrimController).transitionTo(captor.capture(), any());
+ assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR);
+ }
+
+ @Test
+ public void brightnesShowingChanged_flagDisabled_ScrimControllerNotified() {
+ mSceneContainerFlags.setEnabled(false);
+ mCentralSurfaces.registerCallbacks();
+
+ mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
+ mTestScope.getTestScheduler().runCurrent();
+ verify(mScrimController, never()).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ verify(mScrimController, never()).transitionTo(eq(ScrimState.BRIGHTNESS_MIRROR), any());
+ }
+
/**
* Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
* to reconfigure the keyguard to reflect the requested showing/occluded states.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index c1ef1ad..3e9006e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.platform.test.annotations.DisableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -46,6 +47,7 @@
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.Clock;
@@ -157,6 +159,7 @@
}
@Test
+ @DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
public void testHeaderUpdated() {
mRow.setPinned(true);
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
new file mode 100644
index 0000000..dddc712
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimationTest.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import android.os.PowerManager
+import android.os.SystemProperties
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.display.data.repository.fakeDeviceStateRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class FoldLightRevealOverlayAnimationTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope: TestScope = kosmos.testScope
+ private val fakeDeviceStateRepository = kosmos.fakeDeviceStateRepository
+ private val powerInteractor = kosmos.powerInteractor
+ private val fakeAnimationStatusRepository = kosmos.fakeAnimationStatusRepository
+ private val mockControllerFactory = kosmos.fullscreenLightRevealAnimationControllerFactory
+ private val mockFullScreenController = kosmos.fullscreenLightRevealAnimationController
+ private val mockFoldLockSettingAvailabilityProvider =
+ mock<FoldLockSettingAvailabilityProvider>()
+ private val onOverlayReady = mock<Runnable>()
+ private lateinit var foldLightRevealAnimation: FoldLightRevealOverlayAnimation
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(mockFoldLockSettingAvailabilityProvider.isFoldLockBehaviorAvailable)
+ .thenReturn(true)
+ fakeAnimationStatusRepository.onAnimationStatusChanged(true)
+
+ foldLightRevealAnimation =
+ FoldLightRevealOverlayAnimation(
+ kosmos.testDispatcher,
+ fakeDeviceStateRepository,
+ powerInteractor,
+ testScope.backgroundScope,
+ fakeAnimationStatusRepository,
+ mockControllerFactory,
+ mockFoldLockSettingAvailabilityProvider
+ )
+ foldLightRevealAnimation.init()
+ }
+
+ @Test
+ fun foldToScreenOn_playFoldAnimation() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ turnScreenOn()
+
+ verifyFoldAnimationPlayed()
+ }
+
+ @Test
+ fun foldToAod_doNotPlayFoldAnimation() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ emitLastWakefulnessEventStartingToSleep()
+ advanceTimeBy(SHORT_DELAY_MS)
+ turnScreenOn()
+ advanceTimeBy(ANIMATION_DURATION)
+
+ verifyFoldAnimationDidNotPlay()
+ }
+
+ @Test
+ fun foldToScreenOff_doNotPlayFoldAnimation() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ emitLastWakefulnessEventStartingToSleep()
+ advanceTimeBy(SHORT_DELAY_MS)
+ advanceTimeBy(ANIMATION_DURATION)
+
+ verifyFoldAnimationDidNotPlay()
+ }
+
+ @Test
+ fun foldToScreenOnWithDelay_doNotPlayFoldAnimation() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ foldLightRevealAnimation.onScreenTurningOn {}
+ advanceTimeBy(WAIT_FOR_ANIMATION_TIMEOUT_MS)
+ powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ advanceTimeBy(SHORT_DELAY_MS)
+ advanceTimeBy(ANIMATION_DURATION)
+
+ verifyFoldAnimationDidNotPlay()
+ }
+
+ @Test
+ fun immediateUnfoldAfterFold_removeOverlayAfterCancellation() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ foldLightRevealAnimation.onScreenTurningOn {}
+ advanceTimeBy(SHORT_DELAY_MS)
+ advanceTimeBy(ANIMATION_DURATION)
+ fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED)
+ advanceTimeBy(SHORT_DELAY_MS)
+ powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+
+ verifyOverlayWasRemoved()
+ }
+
+ @Test
+ fun foldToScreenOn_removeOverlayAfterCompletion() =
+ testScope.runTest {
+ foldDeviceToScreenOff()
+ turnScreenOn()
+ advanceTimeBy(ANIMATION_DURATION)
+
+ verifyOverlayWasRemoved()
+ }
+
+ @Test
+ fun unfold_immediatelyRunRunnable() =
+ testScope.runTest {
+ foldLightRevealAnimation.onScreenTurningOn(onOverlayReady)
+
+ verify(onOverlayReady).run()
+ }
+
+ private suspend fun TestScope.foldDeviceToScreenOff() {
+ fakeDeviceStateRepository.emit(DeviceState.HALF_FOLDED)
+ powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ advanceTimeBy(SHORT_DELAY_MS)
+ fakeDeviceStateRepository.emit(DeviceState.FOLDED)
+ advanceTimeBy(SHORT_DELAY_MS)
+ powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_OFF)
+ advanceTimeBy(SHORT_DELAY_MS)
+ }
+
+ private fun TestScope.turnScreenOn() {
+ foldLightRevealAnimation.onScreenTurningOn {}
+ advanceTimeBy(SHORT_DELAY_MS)
+ powerInteractor.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ advanceTimeBy(SHORT_DELAY_MS)
+ }
+
+ private fun emitLastWakefulnessEventStartingToSleep() =
+ powerInteractor.setAsleepForTest(PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD)
+
+ private fun verifyFoldAnimationPlayed() =
+ verify(mockFullScreenController, atLeast(1)).updateRevealAmount(any())
+
+ private fun verifyFoldAnimationDidNotPlay() =
+ verify(mockFullScreenController, never()).updateRevealAmount(any())
+
+ private fun verifyOverlayWasRemoved() =
+ verify(mockFullScreenController, atLeast(1)).ensureOverlayRemoved()
+
+ private companion object {
+ const val WAIT_FOR_ANIMATION_TIMEOUT_MS = 2000L
+ val ANIMATION_DURATION: Long
+ get() = SystemProperties.getLong("persist.fold_animation_duration", 200L)
+ const val SHORT_DELAY_MS = 50L
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
index 1125d41..0eba21a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java
@@ -16,9 +16,12 @@
package com.android.systemui.wallpapers;
+import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -32,6 +35,7 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -77,6 +81,7 @@
private Executor mBackgroundExecutor;
private int mColorsProcessed;
+ private int mLocalColorsProcessed;
private int mMiniBitmapUpdatedCount;
private int mActivatedCount;
private int mDeactivatedCount;
@@ -93,6 +98,7 @@
private void resetCounters() {
mColorsProcessed = 0;
+ mLocalColorsProcessed = 0;
mMiniBitmapUpdatedCount = 0;
mActivatedCount = 0;
mDeactivatedCount = 0;
@@ -112,10 +118,14 @@
new Object(),
new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
@Override
+ public void onColorsProcessed() {
+ mColorsProcessed++;
+ }
+ @Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
assertThat(regions.size()).isEqualTo(colors.size());
- mColorsProcessed += regions.size();
+ mLocalColorsProcessed += regions.size();
}
@Override
@@ -148,8 +158,10 @@
.when(spyColorExtractor)
.createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
- doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
- .when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class));
+ WallpaperColors colors = new WallpaperColors(
+ Color.valueOf(0), Color.valueOf(0), Color.valueOf(0));
+ doReturn(colors).when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class));
+ doReturn(colors).when(spyColorExtractor).getWallpaperColors(any(Bitmap.class), anyFloat());
return spyColorExtractor;
}
@@ -244,7 +256,7 @@
assertThat(mActivatedCount).isEqualTo(1);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
- assertThat(mColorsProcessed).isEqualTo(regions.size());
+ assertThat(mLocalColorsProcessed).isEqualTo(regions.size());
spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
@@ -329,12 +341,69 @@
spyColorExtractor.onBitmapChanged(newBitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
}
- assertThat(mColorsProcessed).isEqualTo(regions.size());
+ assertThat(mLocalColorsProcessed).isEqualTo(regions.size());
}
spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
}
+ /**
+ * Test that after the bitmap changes, the colors are computed only if asked via onComputeColors
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColors() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mColorsProcessed).isEqualTo(0);
+ spyColorExtractor.onComputeColors();
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
+ /**
+ * Test that after onComputeColors is called, the colors are computed once the bitmap is loaded
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColorsBeforeBitmapLoaded() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onComputeColors();
+ spyColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
+ /**
+ * Test that after the dim changes, the colors are computed if the bitmap is already loaded
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColorsOnDimChanged() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onDimAmountChanged(0.5f);
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
+ /**
+ * Test that after the dim changes, the colors will be recomputed once the bitmap is loaded
+ */
+ @Test
+ @EnableFlags(FLAG_OFFLOAD_COLOR_EXTRACTION)
+ public void testRecomputeColorsOnDimChangedBeforeBitmapLoaded() {
+ resetCounters();
+ Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onDimAmountChanged(0.3f);
+ spyColorExtractor.onBitmapChanged(bitmap);
+ assertThat(mColorsProcessed).isEqualTo(1);
+ }
+
@Test
public void testCleanUp() {
resetCounters();
@@ -346,6 +415,6 @@
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
spyColorExtractor.cleanUp();
spyColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
- assertThat(mColorsProcessed).isEqualTo(0);
+ assertThat(mLocalColorsProcessed).isEqualTo(0);
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index 5934e04..c1d2ad6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -22,6 +22,7 @@
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
+import com.android.systemui.Flags.FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR
import com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_SYSUI
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
@@ -30,13 +31,14 @@
* that feature. It is also picked up by [SceneContainerRule] to set non-aconfig prerequisites.
*/
@EnableFlags(
- FLAG_SCENE_CONTAINER,
- FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
- FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
FLAG_COMPOSE_LOCKSCREEN,
- FLAG_MEDIA_IN_SCENE_CONTAINER,
+ FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ FLAG_MEDIA_IN_SCENE_CONTAINER,
+ FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+ FLAG_NOTIFICATIONS_HEADS_UP_REFACTOR,
FLAG_PREDICTIVE_BACK_SYSUI,
+ FLAG_SCENE_CONTAINER,
)
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
index e6651a4..f86e9b7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
@@ -20,6 +20,8 @@
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -30,5 +32,7 @@
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
configurationInteractor = configurationInteractor,
animationFlow = keyguardTransitionAnimationFlow,
+ keyguardInteractor = keyguardInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 1b23296..afc8f30 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -49,6 +49,7 @@
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.sceneDataSource
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
@@ -106,6 +107,7 @@
val sharedNotificationContainerInteractor by lazy {
kosmos.sharedNotificationContainerInteractor
}
+ val brightnessMirrorShowingInteractor by lazy { kosmos.brightnessMirrorShowingInteractor }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 1ce2610..0de76c8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -35,7 +35,6 @@
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
import com.android.systemui.qs.footer.foregroundServicesRepository
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.qs.tiles.di.NewQSTileFactory
import com.android.systemui.security.data.repository.securityRepository
import com.android.systemui.settings.userTracker
import com.android.systemui.statusbar.policy.deviceProvisionedController
@@ -49,7 +48,6 @@
QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake)
}
-var Kosmos.newQSTileFactory by Fixture<NewQSTileFactory>()
var Kosmos.qsTileFactory by Fixture<QSFactory>()
val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
index 9ef44c4..b870039 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
@@ -21,7 +21,6 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.qs.external.customTileStatePersister
import com.android.systemui.qs.external.tileLifecycleManagerFactory
-import com.android.systemui.qs.newQSTileFactory
import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository
@@ -29,6 +28,7 @@
import com.android.systemui.qs.pipeline.shared.logging.qsLogger
import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository
import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.qs.tiles.di.newQSTileFactory
import com.android.systemui.settings.userTracker
import com.android.systemui.user.data.repository.userRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
new file mode 100644
index 0000000..5c4b390
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.di
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider
+import com.android.systemui.qs.tiles.viewmodel.qsTileViewModelAdaperFactory
+import com.android.systemui.util.mockito.mock
+import javax.inject.Provider
+import org.mockito.Mockito
+
+var Kosmos.newFactoryTileMap by Kosmos.Fixture { emptyMap<String, Provider<QSTileViewModel>>() }
+
+val Kosmos.newQSTileFactory by
+ Kosmos.Fixture {
+ NewQSTileFactory(
+ qSTileConfigProvider,
+ qsTileViewModelAdaperFactory,
+ newFactoryTileMap,
+ mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
+ mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
+ )
+ }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt
similarity index 68%
copy from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt
index 4e64ab0..1d57979 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderKosmos.kt
@@ -14,15 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.systemui.qs.tiles.viewmodel
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
+import com.android.systemui.kosmos.Kosmos
-import java.util.List;
-
-public interface AslMarshallable {
-
- /** Creates the on-device DOM element from the AslMarshallable Java Object. */
- List<Element> toOdDomElements(Document doc);
-}
+val Kosmos.fakeQSTileConfigProvider by Kosmos.Fixture { FakeQSTileConfigProvider() }
+var Kosmos.qSTileConfigProvider: QSTileConfigProvider by Kosmos.Fixture { fakeQSTileConfigProvider }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt
new file mode 100644
index 0000000..a908765
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.qsTileViewModelAdaperFactory by
+ Kosmos.Fixture {
+ object : QSTileViewModelAdapter.Factory {
+ override fun create(qsTileViewModel: QSTileViewModel): QSTileViewModelAdapter {
+ return QSTileViewModelAdapter(
+ applicationCoroutineScope,
+ mock(),
+ qsTileViewModel,
+ )
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
index 4d902fa..a654d6f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.view.View
+import com.android.systemui.settings.brightness.MirrorController
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -41,6 +42,9 @@
private val _navBarPadding = MutableStateFlow<Int>(0)
val navBarPadding = _navBarPadding.asStateFlow()
+ var brightnessMirrorController: MirrorController? = null
+ private set
+
override var isQsFullyCollapsed: Boolean = true
override suspend fun inflate(context: Context) {
@@ -60,4 +64,12 @@
override suspend fun applyBottomNavBarPadding(padding: Int) {
_navBarPadding.value = padding
}
+
+ override fun requestCloseCustomizer() {
+ _customizing.value = false
+ }
+
+ override fun setBrightnessMirrorController(mirrorController: MirrorController?) {
+ brightnessMirrorController = mirrorController
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
new file mode 100644
index 0000000..8b7e5d8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.classifier.falsingManager
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.settings.brightness.BrightnessSliderController
+import com.android.systemui.util.time.systemClock
+
+/** This factory creates empty mocks. */
+var Kosmos.brightnessSliderControllerFactory by
+ Kosmos.Fixture<BrightnessSliderController.Factory> {
+ BrightnessSliderController.Factory(
+ falsingManager,
+ uiEventLogger,
+ vibratorHelper,
+ systemClock,
+ activityStarter,
+ )
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt
index 5d2b0ad2..6db4649 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/shared/model/ViewPosition.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/data/repository/BrightnessMirrorShowingRepositoryKosmos.kt
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.shared.model
+package com.android.systemui.settings.brightness.data.repository
-/** An offset of view, used to adjust bounds. */
-data class ViewPosition(val left: Int = 0, val top: Int = 0)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.brightnessMirrorShowingRepository by
+ Kosmos.Fixture { BrightnessMirrorShowingRepository() }
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt
similarity index 63%
copy from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java
copy to packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt
index 4e64ab0..8f6b829 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/domain/interactor/BrightnessMirrorShowingInteractorKosmos.kt
@@ -14,15 +14,10 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.systemui.settings.brightness.domain.interactor
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.brightness.data.repository.brightnessMirrorShowingRepository
-import java.util.List;
-
-public interface AslMarshallable {
-
- /** Creates the on-device DOM element from the AslMarshallable Java Object. */
- List<Element> toOdDomElements(Document doc);
-}
+val Kosmos.brightnessMirrorShowingInteractor by
+ Kosmos.Fixture { BrightnessMirrorShowingInteractor(brightnessMirrorShowingRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
new file mode 100644
index 0000000..8fb370c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.brightness.ui.viewmodel
+
+import android.content.res.mainResources
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
+import com.android.systemui.settings.brightnessSliderControllerFactory
+
+val Kosmos.brightnessMirrorViewModel by
+ Kosmos.Fixture {
+ BrightnessMirrorViewModel(
+ brightnessMirrorShowingInteractor,
+ mainResources,
+ brightnessSliderControllerFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FullscreenLightRevealAnimationKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FullscreenLightRevealAnimationKosmos.kt
new file mode 100644
index 0000000..43d6c48
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FullscreenLightRevealAnimationKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.unfold
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+var Kosmos.fullscreenLightRevealAnimationController by Fixture {
+ mock<FullscreenLightRevealAnimationController>()
+}
+var Kosmos.fullscreenLightRevealAnimationControllerFactory by Fixture {
+ var mockControllerFactory = mock<FullscreenLightRevealAnimationController.Factory>()
+ whenever(mockControllerFactory.create(any(), any(), any()))
+ .thenReturn(fullscreenLightRevealAnimationController)
+ mockControllerFactory
+}
diff --git a/packages/overlays/Android.bp b/packages/overlays/Android.bp
index 5e001fb..5075f63 100644
--- a/packages/overlays/Android.bp
+++ b/packages/overlays/Android.bp
@@ -30,9 +30,6 @@
"FontNotoSerifSourceOverlay",
"NavigationBarMode3ButtonOverlay",
"NavigationBarModeGesturalOverlay",
- "NavigationBarModeGesturalOverlayNarrowBack",
- "NavigationBarModeGesturalOverlayWideBack",
- "NavigationBarModeGesturalOverlayExtraWideBack",
"TransparentNavigationBarOverlay",
"NotesRoleEnabledOverlay",
"preinstalled-packages-platform-overlays.xml",
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp
deleted file mode 100644
index 28f9f33..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// Copyright 2019, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-runtime_resource_overlay {
- name: "NavigationBarModeGesturalOverlayExtraWideBack",
- theme: "NavigationBarModeGesturalExtraWideBack",
- product_specific: true,
-}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
deleted file mode 100644
index ba7beba..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.systemui.navbar.gestural_extra_wide_back"
- android:versionCode="1"
- android:versionName="1.0">
- <overlay android:targetPackage="android"
- android:category="com.android.internal.navigation_bar_mode"
- android:priority="1"/>
-
- <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
deleted file mode 100644
index be1f081..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * 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.
- */
--->
-<resources>
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
deleted file mode 100644
index 120a489..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Controls the navigation bar interaction mode:
- 0: 3 button mode (back, home, overview buttons)
- 1: 2 button mode (back, home buttons + swipe up for overview)
- 2: gestures only for back, home and overview -->
- <integer name="config_navBarInteractionMode">2</integer>
-
- <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
- Only applies if the device display is not square. -->
- <bool name="config_navBarCanMove">false</bool>
-
- <!-- Controls whether the navigation bar lets through taps. -->
- <bool name="config_navBarTapThrough">true</bool>
-
- <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
- <bool name="config_imeDrawsImeNavBar">true</bool>
-
- <!-- Controls the size of the back gesture inset. -->
- <dimen name="config_backGestureInset">40dp</dimen>
-
- <!-- Controls whether the navbar needs a scrim with
- {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
- <bool name="config_navBarNeedsScrim">false</bool>
-
- <!-- Controls the opacity of the navigation bar depending on the visibility of the
- various workspace stacks.
- 0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
- 1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
- opaque.
- 2 - Nav bar is never forced opaque.
- -->
- <integer name="config_navBarOpacityMode">2</integer>
-
- <!-- Controls whether seamless rotation should be allowed even though the navbar can move
- (which normally prevents seamless rotation). -->
- <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
- <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
- show. -->
- <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
deleted file mode 100644
index 674bc74..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">24dp</dimen>
- <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">24dp</dimen>
- <!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">24dp</dimen>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_frame_height">48dp</dimen>
- <!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml
deleted file mode 100644
index bbab5e047..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Name of overlay [CHAR LIMIT=64] -->
- <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp
deleted file mode 100644
index f8a5603..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// Copyright 2019, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-runtime_resource_overlay {
- name: "NavigationBarModeGesturalOverlayNarrowBack",
- theme: "NavigationBarModeGesturalNarrowBack",
- product_specific: true,
-}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
deleted file mode 100644
index 8de91c0..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.systemui.navbar.gestural_narrow_back"
- android:versionCode="1"
- android:versionName="1.0">
- <overlay android:targetPackage="android"
- android:category="com.android.internal.navigation_bar_mode"
- android:priority="1"/>
-
- <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
deleted file mode 100644
index be1f081..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * 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.
- */
--->
-<resources>
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
deleted file mode 100644
index c18d892..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Controls the navigation bar interaction mode:
- 0: 3 button mode (back, home, overview buttons)
- 1: 2 button mode (back, home buttons + swipe up for overview)
- 2: gestures only for back, home and overview -->
- <integer name="config_navBarInteractionMode">2</integer>
-
- <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
- Only applies if the device display is not square. -->
- <bool name="config_navBarCanMove">false</bool>
-
- <!-- Controls whether the navigation bar lets through taps. -->
- <bool name="config_navBarTapThrough">true</bool>
-
- <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
- <bool name="config_imeDrawsImeNavBar">true</bool>
-
- <!-- Controls the size of the back gesture inset. -->
- <dimen name="config_backGestureInset">18dp</dimen>
-
- <!-- Controls whether the navbar needs a scrim with
- {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
- <bool name="config_navBarNeedsScrim">false</bool>
-
- <!-- Controls the opacity of the navigation bar depending on the visibility of the
- various workspace stacks.
- 0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
- 1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
- opaque.
- 2 - Nav bar is never forced opaque.
- -->
- <integer name="config_navBarOpacityMode">2</integer>
-
- <!-- Controls whether seamless rotation should be allowed even though the navbar can move
- (which normally prevents seamless rotation). -->
- <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
- <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
- show. -->
- <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
deleted file mode 100644
index 674bc74..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">24dp</dimen>
- <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">24dp</dimen>
- <!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">24dp</dimen>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_frame_height">48dp</dimen>
- <!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
deleted file mode 100644
index bbab5e047..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Name of overlay [CHAR LIMIT=64] -->
- <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp
deleted file mode 100644
index 60ee6d5..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-//
-// Copyright 2019, The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-runtime_resource_overlay {
- name: "NavigationBarModeGesturalOverlayWideBack",
- theme: "NavigationBarModeGesturalWideBack",
- product_specific: true,
-}
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
deleted file mode 100644
index daf4613..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-/**
- * Copyright (c) 2018, 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.
- */
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.internal.systemui.navbar.gestural_wide_back"
- android:versionCode="1"
- android:versionName="1.0">
- <overlay android:targetPackage="android"
- android:category="com.android.internal.navigation_bar_mode"
- android:priority="1"/>
-
- <application android:label="@string/navigation_bar_mode_title" android:hasCode="false"/>
-</manifest>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
deleted file mode 100644
index be1f081..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * 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.
- */
--->
-<resources>
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">false</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
deleted file mode 100644
index 877b5f8..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/config.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Controls the navigation bar interaction mode:
- 0: 3 button mode (back, home, overview buttons)
- 1: 2 button mode (back, home buttons + swipe up for overview)
- 2: gestures only for back, home and overview -->
- <integer name="config_navBarInteractionMode">2</integer>
-
- <!-- Controls whether the nav bar can move from the bottom to the side in landscape.
- Only applies if the device display is not square. -->
- <bool name="config_navBarCanMove">false</bool>
-
- <!-- Controls whether the navigation bar lets through taps. -->
- <bool name="config_navBarTapThrough">true</bool>
-
- <!-- Controls whether the IME renders the back and IME switcher buttons or not. -->
- <bool name="config_imeDrawsImeNavBar">true</bool>
-
- <!-- Controls the size of the back gesture inset. -->
- <dimen name="config_backGestureInset">32dp</dimen>
-
- <!-- Controls whether the navbar needs a scrim with
- {@link Window#setEnsuringNavigationBarContrastWhenTransparent}. -->
- <bool name="config_navBarNeedsScrim">false</bool>
-
- <!-- Controls the opacity of the navigation bar depending on the visibility of the
- various workspace stacks.
- 0 - Nav bar is always opaque when either the freeform stack or docked stack is visible.
- 1 - Nav bar is always translucent when the freeform stack is visible, otherwise always
- opaque.
- 2 - Nav bar is never forced opaque.
- -->
- <integer name="config_navBarOpacityMode">2</integer>
-
- <!-- Controls whether seamless rotation should be allowed even though the navbar can move
- (which normally prevents seamless rotation). -->
- <bool name="config_allowSeamlessRotationDespiteNavBarMoving">true</bool>
-
- <!-- Controls whether the side edge gestures can always trigger the transient nav bar to
- show. -->
- <bool name="config_navBarAlwaysShowOnSideEdgeGesture">true</bool>
-
- <!-- If true, attach the navigation bar to the app during app transition -->
- <bool name="config_attachNavBarToAppDuringTransition">true</bool>
-</resources>
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
deleted file mode 100644
index 674bc74..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/dimens.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_height">24dp</dimen>
- <!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
- <dimen name="navigation_bar_height_landscape">24dp</dimen>
- <!-- Width of the navigation bar when it is placed vertically on the screen -->
- <dimen name="navigation_bar_width">24dp</dimen>
- <!-- Height of the bottom navigation / system bar. -->
- <dimen name="navigation_bar_frame_height">48dp</dimen>
- <!-- The height of the bottom navigation gesture area. -->
- <dimen name="navigation_bar_gesture_height">32dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml b/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
deleted file mode 100644
index bbab5e047..0000000
--- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/**
- * Copyright (c) 2019, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Name of overlay [CHAR LIMIT=64] -->
- <string name="navigation_bar_mode_title" translatable="false">Gestural Navigation Bar</string>
-</resources>
\ No newline at end of file
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index a5b28ad..e77f846f 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -5,6 +5,17 @@
},
{
"name": "RavenwoodBivalentTest_device"
+ },
+ {
+ "name": "SystemUIGoogleTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
}
],
"ravenwood-presubmit": [
diff --git a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
index 5e66b29..83f756e 100644
--- a/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
+++ b/ravenwood/bivalenttest/jni/ravenwood_core_test_jni.cpp
@@ -42,7 +42,7 @@
ALOGI("%s: JNI_OnLoad", __FILE__);
int res = jniRegisterNativeMethods(env,
- "com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest",
+ "com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest",
sMethods, NELEM(sMethods));
if (res < 0) {
return res;
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
similarity index 95%
rename from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java
rename to ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
index c11c1bb..d91e734 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodAndroidApiTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
import static org.junit.Assert.assertEquals;
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
similarity index 95%
rename from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
rename to ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
index 6f2465c..3a24c0e 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.ravenwood.RavenwoodClassRule;
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
similarity index 96%
rename from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
rename to ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
index 21b31d1..aa33dc3 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
import android.platform.test.ravenwood.RavenwoodClassRule;
import android.platform.test.ravenwood.RavenwoodRule;
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
similarity index 95%
rename from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java
rename to ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
index 3b106da..59467e9 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodJniTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
import static junit.framework.Assert.assertEquals;
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
similarity index 95%
rename from ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
rename to ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
index 4b650b4..3edca7e 100644
--- a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodRuleTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.bivalenttest;
+package com.android.ravenwoodtest.bivalenttest;
import android.platform.test.annotations.DisabledOnNonRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
similarity index 96%
rename from ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
rename to ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
index 4ee9a9c..f1e33cb 100644
--- a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
+++ b/ravenwood/coretest/test/com/android/ravenwoodtest/coretest/RavenwoodTestRunnerValidationTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.platform.test.ravenwood.coretest;
+package com.android.ravenwoodtest.coretest;
import android.platform.test.ravenwood.RavenwoodRule;
diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
index 9d47f3a..2fb8074 100644
--- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
@@ -32,7 +32,7 @@
* on a per-method basis.
*
* THIS ANNOTATION CANNOT BE ADDED TO CLASSES AT THIS PONINT.
- * See {@link com.android.platform.test.ravenwood.bivalenttest.RavenwoodClassRuleRavenwoodOnlyTest}
+ * See {@link com.android.ravenwoodtest.bivalenttest.RavenwoodClassRuleRavenwoodOnlyTest}
* for the reason.
*
* The {@code RAVENWOOD_RUN_DISABLED_TESTS} environmental variable won't work because it won't be
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index a2e8ec1..21d8019 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -277,8 +277,11 @@
return false;
}
}
- if (description.getTestClass().getAnnotation(DisabledOnNonRavenwood.class) != null) {
- return false;
+ final var clazz = description.getTestClass();
+ if (clazz != null) {
+ if (clazz.getAnnotation(DisabledOnNonRavenwood.class) != null) {
+ return false;
+ }
}
return true;
}
@@ -310,14 +313,17 @@
}
// Otherwise, consult any class-level annotations
- if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
- return true;
- }
- if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
- return false;
- }
- if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
- return false;
+ final var clazz = description.getTestClass();
+ if (clazz != null) {
+ if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
+ return true;
+ }
+ if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
+ return false;
+ }
+ if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ return false;
+ }
}
// When no annotations have been requested, assume test should be included
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java
similarity index 97%
rename from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java
rename to ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java
index d02fe69..d566977 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
import static com.google.common.truth.Truth.assertThat;
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
similarity index 96%
rename from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
rename to ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
index 0c137d5..aa2b7611 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
import static com.google.common.truth.Truth.assertThat;
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java
similarity index 97%
rename from ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
rename to ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java
index 9566710..fcc6c9c 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.ravenwood.mockito;
+package com.android.ravenwoodtest.mockito;
import static com.google.common.truth.Truth.assertThat;
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java
similarity index 97%
rename from ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java
rename to ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java
index efe468d..f833782 100644
--- a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesDependenciesTest.java
+++ b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.ravenwood;
+package com.android.ravenwoodtest.servicestest;
import static org.junit.Assert.assertEquals;
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
similarity index 98%
rename from ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java
rename to ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index c1dee5d..044239f 100644
--- a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java
+++ b/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.ravenwood;
+package com.android.ravenwoodtest.servicestest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index 467adc7..7a99b60 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -58,6 +58,7 @@
aconfig_declarations {
name: "com_android_server_accessibility_flags",
package: "com.android.server.accessibility",
+ container: "system",
srcs: [
"accessibility.aconfig",
],
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 04b19ff..1d6399e 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.accessibility"
+container: "system"
# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 0811c87..bbbc4ae 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2734,10 +2734,13 @@
userState.mComponentNameToServiceMap;
boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId);
+ // Store the list of installed services.
+ mTempComponentNameSet.clear();
for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
ComponentName componentName = ComponentName.unflattenFromString(
installedService.getId());
+ mTempComponentNameSet.add(componentName);
AccessibilityServiceConnection service = componentNameToServiceMap.get(componentName);
@@ -2797,6 +2800,25 @@
audioManager.setAccessibilityServiceUids(mTempIntArray);
}
mActivityTaskManagerService.setAccessibilityServiceUids(mTempIntArray);
+
+ // If any services have been removed, remove them from the enabled list and the touch
+ // exploration granted list.
+ boolean anyServiceRemoved =
+ userState.mEnabledServices.removeIf((comp) -> !mTempComponentNameSet.contains(comp))
+ || userState.mTouchExplorationGrantedServices.removeIf(
+ (comp) -> !mTempComponentNameSet.contains(comp));
+ if (anyServiceRemoved) {
+ // Update the enabled services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userState.mEnabledServices,
+ userState.mUserId);
+ // Update the touch exploration granted services setting.
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ userState.mTouchExplorationGrantedServices,
+ userState.mUserId);
+ }
updateAccessibilityEnabledSettingLocked(userState);
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 4dae6d5..30e4a3e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -52,6 +52,7 @@
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.ecm.EnhancedConfirmationManager;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.IAssociationRequestCallback;
@@ -64,6 +65,7 @@
import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -80,6 +82,7 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.flags.Flags;
import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Slog;
@@ -448,15 +451,26 @@
}
return Binder.withCleanCallingIdentity(() -> {
+ final Intent intent;
if (!isRestrictedSettingsAllowed(getContext(), callingPackage, callingUid)) {
Slog.e(TAG, "Side loaded app must enable restricted "
+ "setting before request the notification access");
- return null;
+ if (Flags.enhancedConfirmationModeApisEnabled()) {
+ intent = getContext()
+ .getSystemService(EnhancedConfirmationManager.class)
+ .createRestrictedSettingDialogIntent(callingPackage,
+ AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
+ } else {
+ return null;
+ }
+ } else {
+ intent = NotificationAccessConfirmationActivityContract.launcherIntent(
+ getContext(), userId, component);
}
+
return PendingIntent.getActivityAsUser(getContext(),
0 /* request code */,
- NotificationAccessConfirmationActivityContract.launcherIntent(
- getContext(), userId, component),
+ intent,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_CANCEL_CURRENT,
null /* options */,
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index d7e766e..f397814 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -16,6 +16,8 @@
package com.android.server.companion.utils;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_SCAN;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
@@ -209,7 +211,9 @@
*/
public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context) {
if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
- != PERMISSION_GRANTED) {
+ != PERMISSION_GRANTED
+ || context.checkCallingPermission(BLUETOOTH_SCAN) != PERMISSION_GRANTED
+ || context.checkCallingPermission(BLUETOOTH_CONNECT) != PERMISSION_GRANTED) {
throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
+ "permissions to request observing device presence base on the UUID");
}
diff --git a/services/companion/java/com/android/server/companion/virtual/Android.bp b/services/companion/java/com/android/server/companion/virtual/Android.bp
index 4a2030f..66313e6 100644
--- a/services/companion/java/com/android/server/companion/virtual/Android.bp
+++ b/services/companion/java/com/android/server/companion/virtual/Android.bp
@@ -10,6 +10,7 @@
aconfig_declarations {
name: "virtualdevice_flags",
package: "com.android.server.companion.virtual",
+ container: "system",
srcs: [
"flags.aconfig",
],
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 06431fc..215f640 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -28,8 +28,6 @@
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
-import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
import android.annotation.EnforcePermission;
@@ -1156,8 +1154,8 @@
Flags.vdmCustomHome() ? mParams.getHomeComponent() : null;
final GenericWindowPolicyController gwpc = new GenericWindowPolicyController(
- FLAG_SECURE,
- SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
mAttributionSource,
getAllowedUserHandles(),
activityLaunchAllowedByDefault,
@@ -1271,7 +1269,7 @@
// if the secure window is shown on a non-secure virtual display.
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
Display display = displayManager.getDisplay(displayId);
- if ((display.getFlags() & FLAG_SECURE) == 0) {
+ if ((display.getFlags() & Display.FLAG_SECURE) == 0) {
showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window,
Toast.LENGTH_LONG, mContext.getMainLooper());
diff --git a/services/companion/java/com/android/server/companion/virtual/flags.aconfig b/services/companion/java/com/android/server/companion/virtual/flags.aconfig
index 6297e91..616f5d0 100644
--- a/services/companion/java/com/android/server/companion/virtual/flags.aconfig
+++ b/services/companion/java/com/android/server/companion/virtual/flags.aconfig
@@ -1,6 +1,7 @@
# OLD PACKAGE, DO NOT USE: Prefer `flags.aconfig` in core/java/android/companion/virtual
# (or other custom files) to define your flags
package: "com.android.server.companion.virtual"
+container: "system"
flag {
name: "dump_history"
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c7d9942..fd7e4ab 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -254,6 +254,7 @@
"net_flags_lib",
"stats_flags_lib",
"core_os_flags_lib",
+ "connectivity_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index ac19d8b..4694e9f 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -70,6 +70,8 @@
NotificationListener mNotificationListener;
@Nullable
private MediaProjectionManager mProjectionManager;
+
+ @GuardedBy("mSensitiveContentProtectionLock")
@Nullable
private MediaProjectionSession mMediaProjectionSession;
@@ -98,6 +100,48 @@
mIsExempted = isExempted;
mSessionId = sessionId;
}
+
+ public void logProjectionSessionStart() {
+ FrameworkStatsLog.write(
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+ mSessionId,
+ mUid,
+ mIsExempted,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+ );
+ }
+
+ public void logProjectionSessionStop() {
+ FrameworkStatsLog.write(
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
+ mSessionId,
+ mUid,
+ mIsExempted,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP,
+ SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
+ );
+ }
+
+ public void logAppBlocked(int uid) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
+ mSessionId,
+ uid,
+ mUid,
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED
+ );
+ }
+
+ public void logAppUnblocked(int uid) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
+ mSessionId,
+ uid,
+ mUid,
+ FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
+ );
+ }
}
private final MediaProjectionManager.Callback mProjectionCallback =
@@ -112,28 +156,11 @@
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
- FrameworkStatsLog.write(
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
- mMediaProjectionSession.mSessionId,
- mMediaProjectionSession.mUid,
- mMediaProjectionSession.mIsExempted,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
- );
}
@Override
public void onStop(MediaProjectionInfo info) {
if (DEBUG) Log.d(TAG, "onStop projection: " + info);
- FrameworkStatsLog.write(
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION,
- mMediaProjectionSession.mSessionId,
- mMediaProjectionSession.mUid,
- mMediaProjectionSession.mIsExempted,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP,
- SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__FRAMEWORKS
- );
-
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
"SensitiveContentProtectionManagerService.onProjectionStop");
try {
@@ -242,16 +269,18 @@
DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 0) != 0;
int uid = mPackageManagerInternal.getPackageUid(projectionInfo.getPackageName(), 0,
projectionInfo.getUserHandle().getIdentifier());
- mMediaProjectionSession = new MediaProjectionSession(
- uid, isPackageExempted || isFeatureDisabled, new Random().nextLong());
-
- if (isPackageExempted || isFeatureDisabled) {
- Log.w(TAG, "projection session is exempted, package ="
- + projectionInfo.getPackageName() + ", isFeatureDisabled=" + isFeatureDisabled);
- return;
- }
-
synchronized (mSensitiveContentProtectionLock) {
+ mMediaProjectionSession = new MediaProjectionSession(
+ uid, isPackageExempted || isFeatureDisabled, new Random().nextLong());
+ mMediaProjectionSession.logProjectionSessionStart();
+
+ if (isPackageExempted || isFeatureDisabled) {
+ Log.w(TAG, "projection session is exempted, package ="
+ + projectionInfo.getPackageName() + ", isFeatureDisabled="
+ + isFeatureDisabled);
+ return;
+ }
+
mProjectionActive = true;
if (sensitiveNotificationAppProtection()) {
updateAppsThatShouldBlockScreenCapture();
@@ -266,7 +295,10 @@
private void onProjectionEnd() {
synchronized (mSensitiveContentProtectionLock) {
mProjectionActive = false;
- mMediaProjectionSession = null;
+ if (mMediaProjectionSession != null) {
+ mMediaProjectionSession.logProjectionSessionStop();
+ mMediaProjectionSession = null;
+ }
// notify windowmanager to clear any sensitive notifications observed during projection
// session
@@ -437,22 +469,14 @@
packageInfos.add(packageInfo);
if (isShowingSensitiveContent) {
mWindowManager.addBlockScreenCaptureForApps(packageInfos);
- FrameworkStatsLog.write(
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
- mMediaProjectionSession.mSessionId,
- uid,
- mMediaProjectionSession.mUid,
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__BLOCKED
- );
+ if (mMediaProjectionSession != null) {
+ mMediaProjectionSession.logAppBlocked(uid);
+ }
} else {
mWindowManager.removeBlockScreenCaptureForApps(packageInfos);
- FrameworkStatsLog.write(
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION,
- mMediaProjectionSession.mSessionId,
- uid,
- mMediaProjectionSession.mUid,
- FrameworkStatsLog.SENSITIVE_CONTENT_APP_PROTECTION__STATE__UNBLOCKED
- );
+ if (mMediaProjectionSession != null) {
+ mMediaProjectionSession.logAppUnblocked(uid);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 9c4c634..baae40b 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -62,6 +62,18 @@
"file_patterns": ["SensorPrivacyService\\.java"]
},
{
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.SensitiveContentProtectionManagerServiceContentTest"
+ },
+ {
+ "include-filter": "com.android.server.SensitiveContentProtectionManagerServiceNotificationTest"
+ }
+ ],
+ "file_patterns": ["SensitiveContentProtectionManagerService\\.java"]
+ },
+ {
"name": "FrameworksServicesTests",
"options": [
{
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 586b095..603a95c 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -897,6 +897,11 @@
}
}
for (String packageNameToNotify : accountRemovedReceivers) {
+ int currentVisibility =
+ resolveAccountVisibility(account, packageNameToNotify, accounts);
+ if (isVisible(currentVisibility)) {
+ continue;
+ }
sendAccountRemovedBroadcast(
account,
packageNameToNotify,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b20135c..ad15ea9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -635,7 +635,8 @@
static final String EXTRA_DESCRIPTION = "android.intent.extra.DESCRIPTION";
static final String EXTRA_BUGREPORT_TYPE = "android.intent.extra.BUGREPORT_TYPE";
static final String EXTRA_BUGREPORT_NONCE = "android.intent.extra.BUGREPORT_NONCE";
-
+ static final String EXTRA_EXTRA_ATTACHMENT_URI =
+ "android.intent.extra.EXTRA_ATTACHMENT_URI";
/**
* It is now required for apps to explicitly set either
* {@link android.content.Context#RECEIVER_EXPORTED} or
@@ -724,6 +725,9 @@
// Whether we should use SCHED_FIFO for UI and RenderThreads.
final boolean mUseFifoUiScheduling;
+ /** Whether some specified important processes are allowed to use FIFO priority. */
+ boolean mAllowSpecifiedFifoScheduling = true;
+
@GuardedBy("this")
private final SparseArray<IUnsafeIntentStrictModeCallback>
mStrictModeCallbacks = new SparseArray<>();
@@ -1047,6 +1051,10 @@
@GuardedBy("this")
final SparseArray<ImportanceToken> mImportantProcesses = new SparseArray<ImportanceToken>();
+ /** The processes that are allowed to use SCHED_FIFO prorioty. */
+ @GuardedBy("mProcLock")
+ final ArrayList<ProcessRecord> mSpecifiedFifoProcesses = new ArrayList<>();
+
/**
* List of records for processes that someone had tried to start before the
* system was ready. We don't start them at that point, but ensure they
@@ -4418,7 +4426,9 @@
packageName, null, userId);
}
- if (packageName == null || uninstalling || packageStateStopped) {
+ final boolean clearPendingIntentsForStoppedApp = (android.content.pm.Flags.stayStopped()
+ && packageStateStopped);
+ if (packageName == null || uninstalling || clearPendingIntentsForStoppedApp) {
didSomething |= mPendingIntentController.removePendingIntentsForPackage(
packageName, userId, appId, doit);
}
@@ -7654,6 +7664,16 @@
*/
public void requestBugReportWithDescription(@Nullable String shareTitle,
@Nullable String shareDescription, int bugreportType, long nonce) {
+ requestBugReportWithDescription(shareTitle, shareDescription, bugreportType, nonce, null);
+ }
+
+ /**
+ * Takes a bugreport using bug report API ({@code BugreportManager}) which gets
+ * triggered by sending a broadcast to Shell. Optionally adds an extra attachment.
+ */
+ public void requestBugReportWithDescription(@Nullable String shareTitle,
+ @Nullable String shareDescription, int bugreportType, long nonce,
+ @Nullable Uri extraAttachment) {
String type = null;
switch (bugreportType) {
case BugreportParams.BUGREPORT_MODE_FULL:
@@ -7708,6 +7728,10 @@
triggerShellBugreport.setPackage(SHELL_APP_PACKAGE);
triggerShellBugreport.putExtra(EXTRA_BUGREPORT_TYPE, bugreportType);
triggerShellBugreport.putExtra(EXTRA_BUGREPORT_NONCE, nonce);
+ if (extraAttachment != null) {
+ triggerShellBugreport.putExtra(EXTRA_EXTRA_ATTACHMENT_URI, extraAttachment);
+ triggerShellBugreport.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
if (shareTitle != null) {
@@ -7761,6 +7785,15 @@
}
/**
+ * Takes an interactive bugreport with a progress notification. Also attaches given file uri.
+ */
+ @Override
+ public void requestBugReportWithExtraAttachment(@NonNull Uri extraAttachment) {
+ requestBugReportWithDescription(null, null, BugreportParams.BUGREPORT_MODE_INTERACTIVE, 0L,
+ extraAttachment);
+ }
+
+ /**
* Takes an interactive bugreport with a progress notification. Also, shows the given title and
* description on the final share notification
*/
@@ -8218,6 +8251,27 @@
return false;
}
+ /**
+ * Switches the priority between SCHED_FIFO and SCHED_OTHER for the main thread and render
+ * thread of the given process.
+ */
+ @GuardedBy("mProcLock")
+ static void setFifoPriority(@NonNull ProcessRecord app, boolean enable) {
+ final int pid = app.getPid();
+ final int renderThreadTid = app.getRenderThreadTid();
+ if (enable) {
+ scheduleAsFifoPriority(pid, true /* suppressLogs */);
+ if (renderThreadTid != 0) {
+ scheduleAsFifoPriority(renderThreadTid, true /* suppressLogs */);
+ }
+ } else {
+ scheduleAsRegularPriority(pid, true /* suppressLogs */);
+ if (renderThreadTid != 0) {
+ scheduleAsRegularPriority(renderThreadTid, true /* suppressLogs */);
+ }
+ }
+ }
+
@Override
public void setRenderThread(int tid) {
synchronized (mProcLock) {
@@ -8243,7 +8297,7 @@
// promote to FIFO now
if (proc.mState.getCurrentSchedulingGroup() == ProcessList.SCHED_GROUP_TOP_APP) {
if (DEBUG_OOM_ADJ) Slog.d("UI_FIFO", "Promoting " + tid + "out of band");
- if (mUseFifoUiScheduling) {
+ if (proc.useFifoUiScheduling()) {
setThreadScheduler(proc.getRenderThreadTid(),
SCHED_FIFO | SCHED_RESET_ON_FORK, 1);
} else {
@@ -11280,6 +11334,9 @@
if (mAlwaysFinishActivities) {
pw.println(" mAlwaysFinishActivities=" + mAlwaysFinishActivities);
}
+ if (mAllowSpecifiedFifoScheduling) {
+ pw.println(" mAllowSpecifiedFifoScheduling=true");
+ }
if (dumpAll) {
pw.println(" Total persistent processes: " + numPers);
pw.println(" mProcessesReady=" + mProcessesReady
@@ -17348,6 +17405,12 @@
}
}
}
+
+ if (com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()) {
+ synchronized (mProcLock) {
+ adjustFifoProcessesIfNeeded(uid, !active /* allowFifo */);
+ }
+ }
}
final boolean isCameraActiveForUid(@UserIdInt int uid) {
@@ -17356,6 +17419,34 @@
}
}
+ /**
+ * This is called when the given uid is using camera. If the uid has top process state, then
+ * cancel the FIFO priority of the high priority processes.
+ */
+ @VisibleForTesting
+ @GuardedBy("mProcLock")
+ void adjustFifoProcessesIfNeeded(int preemptiveUid, boolean allowSpecifiedFifo) {
+ if (allowSpecifiedFifo == mAllowSpecifiedFifoScheduling) {
+ return;
+ }
+ if (!allowSpecifiedFifo) {
+ final UidRecord uidRec = mProcessList.mActiveUids.get(preemptiveUid);
+ if (uidRec == null || uidRec.getCurProcState() > PROCESS_STATE_TOP) {
+ // To avoid frequent switching by background camera usages, e.g. face unlock,
+ // face detection (auto rotation), screen attention (keep screen on).
+ return;
+ }
+ }
+ mAllowSpecifiedFifoScheduling = allowSpecifiedFifo;
+ for (int i = mSpecifiedFifoProcesses.size() - 1; i >= 0; i--) {
+ final ProcessRecord proc = mSpecifiedFifoProcesses.get(i);
+ if (proc.mState.getSetSchedGroup() != ProcessList.SCHED_GROUP_TOP_APP) {
+ continue;
+ }
+ setFifoPriority(proc, allowSpecifiedFifo /* enable */);
+ }
+ }
+
@GuardedBy("this")
final void doStopUidLocked(int uid, final UidRecord uidRec) {
mServices.stopInBackgroundLocked(uid);
@@ -17622,7 +17713,8 @@
@Override
public boolean dumpHeap(String process, int userId, boolean managed, boolean mallocInfo,
- boolean runGc, String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+ boolean runGc, String dumpBitmaps,
+ String path, ParcelFileDescriptor fd, RemoteCallback finishCallback) {
try {
// note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
// its own permission (same as profileControl).
@@ -17656,7 +17748,8 @@
}
}, null);
- thread.dumpHeap(managed, mallocInfo, runGc, path, fd, intermediateCallback);
+ thread.dumpHeap(managed, mallocInfo, runGc, dumpBitmaps,
+ path, fd, intermediateCallback);
fd = null;
return true;
}
@@ -17868,9 +17961,35 @@
mUserController.setStopUserOnSwitch(value);
}
+ /** @deprecated use {@link #stopUserWithCallback(int, IStopUserCallback)} instead */
+ @Deprecated
@Override
- public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
- return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false,
+ public int stopUser(final int userId,
+ boolean stopProfileRegardlessOfParent, final IStopUserCallback callback) {
+ return stopUserExceptCertainProfiles(userId, stopProfileRegardlessOfParent, callback);
+ }
+
+ /** Stops the given user. */
+ @Override
+ public int stopUserWithCallback(@UserIdInt int userId, @Nullable IStopUserCallback callback) {
+ return mUserController.stopUser(userId, /* allowDelayedLocking= */ false,
+ /* callback= */ callback, /* keyEvictedCallback= */ null);
+ }
+
+ /**
+ * Stops the given user.
+ *
+ * Usually, callers can just use @link{#stopUserWithCallback(int, IStopUserCallback)} instead.
+ *
+ * @param stopProfileRegardlessOfParent whether to stop the profile regardless of who its
+ * parent is, e.g. even if the parent is the current user;
+ * its value is irrelevant for non-profile users.
+ */
+ @Override
+ public int stopUserExceptCertainProfiles(@UserIdInt int userId,
+ boolean stopProfileRegardlessOfParent, @Nullable IStopUserCallback callback) {
+ return mUserController.stopUser(userId,
+ stopProfileRegardlessOfParent, /* allowDelayedLocking= */ false,
/* callback= */ callback, /* keyEvictedCallback= */ null);
}
@@ -17879,11 +17998,9 @@
* stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true.
*
* <p>When delayed locking is not enabled through the overlay, this call becomes the same
- * with {@link #stopUser(int, boolean, IStopUserCallback)} call.
+ * with {@link #stopUserWithCallback(int, IStopUserCallback)} call.
*
* @param userId User id to stop.
- * @param force Force stop the user even if the user is related with system user or current
- * user.
* @param callback Callback called when user has stopped.
*
* @return {@link ActivityManager#USER_OP_SUCCESS} when user is stopped successfully. Returns
@@ -17893,9 +18010,8 @@
// TODO(b/302662311): Add javadoc changes corresponding to the user property that allows
// delayed locking behavior once the private space flag is finalized.
@Override
- public int stopUserWithDelayedLocking(final int userId, boolean force,
- final IStopUserCallback callback) {
- return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ true,
+ public int stopUserWithDelayedLocking(@UserIdInt int userId, IStopUserCallback callback) {
+ return mUserController.stopUser(userId, /* allowDelayedLocking= */ true,
/* callback= */ callback, /* keyEvictedCallback= */ null);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 5a97e87..e70722c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1239,6 +1239,7 @@
final PrintWriter err = getErrPrintWriter();
boolean managed = true;
boolean mallocInfo = false;
+ String dumpBitmaps = null;
int userId = UserHandle.USER_CURRENT;
boolean runGc = false;
@@ -1257,6 +1258,11 @@
} else if (opt.equals("-m")) {
managed = false;
mallocInfo = true;
+ } else if (opt.equals("-b")) {
+ dumpBitmaps = getNextArg();
+ if (dumpBitmaps == null) {
+ dumpBitmaps = "png"; // default to PNG in dumping bitmaps
+ }
} else {
err.println("Error: Unknown option: " + opt);
return -1;
@@ -1288,8 +1294,8 @@
}
}, null);
- if (!mInterface.dumpHeap(process, userId, managed, mallocInfo, runGc, heapFile, fd,
- finishCallback)) {
+ if (!mInterface.dumpHeap(process, userId, managed, mallocInfo, runGc, dumpBitmaps,
+ heapFile, fd, finishCallback)) {
err.println("HEAP DUMP FAILED on process " + process);
return -1;
}
@@ -2554,7 +2560,8 @@
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"shell_runStopUser-" + userId + "-[stopUser]");
try {
- int res = mInterface.stopUser(userId, force, callback);
+ int res = mInterface.stopUserExceptCertainProfiles(
+ userId, /* stopProfileRegardlessOfParent= */ force, callback);
if (res != ActivityManager.USER_OP_SUCCESS) {
String txt = "";
switch (res) {
@@ -4284,11 +4291,14 @@
pw.println(" --user <USER_ID> | current: When supplying a process name,");
pw.println(" specify user of process to profile; uses current user if not");
pw.println(" specified.");
- pw.println(" dumpheap [--user <USER_ID> current] [-n] [-g] <PROCESS> <FILE>");
+ pw.println(" dumpheap [--user <USER_ID> current] [-n] [-g] [-b <format>] ");
+ pw.println(" <PROCESS> <FILE>");
pw.println(" Dump the heap of a process. The given <PROCESS> argument may");
pw.println(" be either a process name or pid. Options are:");
pw.println(" -n: dump native heap instead of managed heap");
pw.println(" -g: force GC before dumping the heap");
+ pw.println(" -b <format>: dump contents of bitmaps in the format specified,");
+ pw.println(" which can be \"png\", \"jpg\" or \"webp\".");
pw.println(" --user <USER_ID> | current: When supplying a process name,");
pw.println(" specify user of process to dump; uses current user if not specified.");
pw.println(" set-debug-app [-w] [--persistent] <PACKAGE>");
@@ -4385,7 +4395,7 @@
pw.println(" Stop execution of USER_ID, not allowing it to run any");
pw.println(" code until a later explicit start or switch to it.");
pw.println(" -w: wait for stop-user to complete.");
- pw.println(" -f: force stop even if there are related users that cannot be stopped.");
+ pw.println(" -f: force stop, even if user has an unstoppable parent.");
pw.println(" is-user-stopped <USER_ID>");
pw.println(" Returns whether <USER_ID> has been stopped or not.");
pw.println(" get-started-user-state <USER_ID>");
diff --git a/services/core/java/com/android/server/am/Android.bp b/services/core/java/com/android/server/am/Android.bp
index af1200e..0294ffe 100644
--- a/services/core/java/com/android/server/am/Android.bp
+++ b/services/core/java/com/android/server/am/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "am_flags",
package: "com.android.server.am",
+ container: "system",
srcs: ["*.aconfig"],
}
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 51aae77..6c16fba0 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -1051,7 +1051,9 @@
+ mProfile.mApp + " to " + mDumpUri.getPath());
}
thread.dumpHeap(/* managed= */ true,
- /* mallocInfo= */ false, /* runGc= */ false,
+ /* mallocInfo= */ false,
+ /* runGc= */ false,
+ /* dumpbitmaps= */ null,
mDumpUri.getPath(), fd,
/* finishCallback= */ null);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index a656287..b517631 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -9,7 +9,6 @@
sudheersai@google.com
suprabh@google.com
varunshah@google.com
-kwekua@google.com
bookatz@google.com
jji@google.com
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5a750c2..ea7a21d 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -72,7 +72,6 @@
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
import static android.media.audio.Flags.roForegroundAudioControl;
-import static android.os.Process.SCHED_OTHER;
import static android.os.Process.THREAD_GROUP_BACKGROUND;
import static android.os.Process.THREAD_GROUP_DEFAULT;
import static android.os.Process.THREAD_GROUP_RESTRICTED;
@@ -81,7 +80,6 @@
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
import static android.os.Process.setProcessGroup;
import static android.os.Process.setThreadPriority;
-import static android.os.Process.setThreadScheduler;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
@@ -3315,22 +3313,10 @@
// do nothing if we already switched to RT
if (oldSchedGroup != SCHED_GROUP_TOP_APP) {
app.getWindowProcessController().onTopProcChanged();
- if (mService.mUseFifoUiScheduling) {
+ if (app.useFifoUiScheduling()) {
// Switch UI pipeline for app to SCHED_FIFO
state.setSavedPriority(Process.getThreadPriority(app.getPid()));
- mService.scheduleAsFifoPriority(app.getPid(), true);
- if (renderThreadTid != 0) {
- mService.scheduleAsFifoPriority(renderThreadTid,
- /* suppressLogs */true);
- if (DEBUG_OOM_ADJ) {
- Slog.d("UI_FIFO", "Set RenderThread (TID " +
- renderThreadTid + ") to FIFO");
- }
- } else {
- if (DEBUG_OOM_ADJ) {
- Slog.d("UI_FIFO", "Not setting RenderThread TID");
- }
- }
+ ActivityManagerService.setFifoPriority(app, true /* enable */);
} else {
// Boost priority for top app UI and render threads
setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
@@ -3347,22 +3333,10 @@
} else if (oldSchedGroup == SCHED_GROUP_TOP_APP
&& curSchedGroup != SCHED_GROUP_TOP_APP) {
app.getWindowProcessController().onTopProcChanged();
- if (mService.mUseFifoUiScheduling) {
- try {
- // Reset UI pipeline to SCHED_OTHER
- setThreadScheduler(app.getPid(), SCHED_OTHER, 0);
- setThreadPriority(app.getPid(), state.getSavedPriority());
- if (renderThreadTid != 0) {
- setThreadScheduler(renderThreadTid,
- SCHED_OTHER, 0);
- }
- } catch (IllegalArgumentException e) {
- Slog.w(TAG,
- "Failed to set scheduling policy, thread does not exist:\n"
- + e);
- } catch (SecurityException e) {
- Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
- }
+ if (app.useFifoUiScheduling()) {
+ // Reset UI pipeline to SCHED_OTHER
+ ActivityManagerService.setFifoPriority(app, false /* enable */);
+ setThreadPriority(app.getPid(), state.getSavedPriority());
} else {
// Reset priority for top app UI and render threads
setThreadPriority(app.getPid(), 0);
@@ -3557,7 +3531,7 @@
// {@link SCHED_GROUP_TOP_APP}. We don't check render thread because it
// is not ready when attaching.
app.getWindowProcessController().onTopProcChanged();
- if (mService.mUseFifoUiScheduling) {
+ if (app.useFifoUiScheduling()) {
mService.scheduleAsFifoPriority(app.getPid(), true);
} else {
setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST);
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index a8fe734..ea92571 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -25,6 +25,8 @@
import java.io.PrintWriter;
+import dalvik.annotation.optimization.NeverCompile;
+
/**
* The state info of app when it's cached, used by the optimizer.
*/
@@ -340,6 +342,7 @@
}
@GuardedBy("mProcLock")
+ @NeverCompile
void dump(PrintWriter pw, String prefix, long nowUptime) {
pw.print(prefix); pw.print("lastCompactTime="); pw.print(mLastCompactTime);
pw.print(" lastCompactProfile=");
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index b939089..0816527 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -745,6 +745,9 @@
mOnewayThread = thread;
}
mWindowProcessController.setThread(thread);
+ if (mWindowProcessController.useFifoUiScheduling()) {
+ mService.mSpecifiedFifoProcesses.add(this);
+ }
}
@GuardedBy({"mService", "mProcLock"})
@@ -752,9 +755,19 @@
mThread = null;
mOnewayThread = null;
mWindowProcessController.setThread(null);
+ if (mWindowProcessController.useFifoUiScheduling()) {
+ mService.mSpecifiedFifoProcesses.remove(this);
+ }
mProfile.onProcessInactive(tracker);
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ boolean useFifoUiScheduling() {
+ return mService.mUseFifoUiScheduling
+ || (mService.mAllowSpecifiedFifoScheduling
+ && mWindowProcessController.useFifoUiScheduling());
+ }
+
@GuardedBy("mService")
int getDyingPid() {
return mDyingPid;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 60a8b50..dd4cee4 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -246,6 +246,7 @@
*
* <p>Note: Current and system user (and their related profiles) are never stopped when
* switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
+ // TODO(b/310249114): Strongly consider *not* exempting the SYSTEM user's profile.
*/
@GuardedBy("mLock")
private int mMaxRunningUsers;
@@ -578,7 +579,8 @@
// from outside.
Slogf.i(TAG, "Too many running users (%d). Attempting to stop user %d",
currentlyRunningLru.size(), userId);
- if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
+ if (stopUsersLU(userId,
+ /* stopProfileRegardlessOfParent= */ false, /* allowDelayedLocking= */ true,
/* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
== USER_OP_SUCCESS) {
// Technically, stopUsersLU can remove more than one user when stopping a parent.
@@ -875,7 +877,7 @@
Slogf.i(TAG, "Stopping pre-created user " + userInfo.toFullString());
// Pre-created user was started right after creation so services could properly
// intialize it; it should be stopped right away as it's not really a "real" user.
- stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
+ stopUser(userInfo.id, /* allowDelayedLocking= */ false,
/* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
return;
}
@@ -930,7 +932,7 @@
}
int restartUser(final int userId, @UserStartMode int userStartMode) {
- return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false,
+ return stopUser(userId, /* allowDelayedLocking= */ false,
/* stopUserCallback= */ null, new KeyEvictedCallback() {
@Override
public void keyEvicted(@UserIdInt int userId) {
@@ -966,18 +968,24 @@
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
- return stopUsersLU(userId, /* force= */ true, /* allowDelayedLocking= */
- false, /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
+ return stopUsersLU(userId, /* allowDelayedLocking= */ false,
+ /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
== ActivityManager.USER_OP_SUCCESS;
}
}
- int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
+ int stopUser(final int userId, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
+ return stopUser(userId, true, allowDelayedLocking, stopUserCallback, keyEvictedCallback);
+ }
+
+ int stopUser(final int userId,
+ final boolean stopProfileRegardlessOfParent, final boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("UserController"
- + (force ? "-force" : "")
+ + (stopProfileRegardlessOfParent ? "-stopProfileRegardlessOfParent" : "")
+ (allowDelayedLocking ? "-allowDelayedLocking" : "")
+ (stopUserCallback != null ? "-withStopUserCallback" : "")
+ "-" + userId + "-[stopUser]");
@@ -987,20 +995,32 @@
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
- return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
- keyEvictedCallback);
+ return stopUsersLU(userId, stopProfileRegardlessOfParent, allowDelayedLocking,
+ stopUserCallback, keyEvictedCallback);
}
} finally {
t.traceEnd();
}
}
+ /** Stops the user along with its profiles. */
+ @GuardedBy("mLock")
+ private int stopUsersLU(final int userId, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
+ return stopUsersLU(userId, /* stopProfileRegardlessOfParent= */ true,
+ allowDelayedLocking, stopUserCallback, keyEvictedCallback);
+ }
+
/**
* Stops the user along with its profiles. The method calls
* {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
+ *
+ * @param stopProfileRegardlessOfParent whether to stop the profile regardless of who its
+ * parent is, e.g. even if the parent is the current user
*/
@GuardedBy("mLock")
- private int stopUsersLU(final int userId, boolean force, boolean allowDelayedLocking,
+ private int stopUsersLU(final int userId,
+ boolean stopProfileRegardlessOfParent, boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
if (userId == UserHandle.USER_SYSTEM) {
return USER_OP_ERROR_IS_SYSTEM;
@@ -1008,34 +1028,28 @@
if (isCurrentUserLU(userId)) {
return USER_OP_IS_CURRENT;
}
- // TODO(b/324647580): Refactor the idea of "force" and clean up. In the meantime...
- final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
- if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) {
- if ((UserHandle.USER_SYSTEM == parentId || isCurrentUserLU(parentId)) && !force) {
- return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
+ if (!stopProfileRegardlessOfParent) {
+ final int parentId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+ if (parentId != UserInfo.NO_PROFILE_GROUP_ID && parentId != userId) {
+ // TODO(b/310249114): Strongly consider *not* exempting the SYSTEM user's profile.
+ if ((UserHandle.USER_SYSTEM == parentId || isCurrentUserLU(parentId))) {
+ return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
+ }
}
}
- TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- int[] usersToStop = getUsersToStopLU(userId);
- // If one of related users is system or current, no related users should be stopped
+ final int[] usersToStop = getUsersToStopLU(userId);
+
+ // Final safety check: abort if one of the users we would plan to stop must not be stopped.
+ // This should be impossible in the current code, but just in case.
for (int relatedUserId : usersToStop) {
if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) {
- if (DEBUG_MU) {
- Slogf.i(TAG, "stopUsersLocked cannot stop related user " + relatedUserId);
- }
- // We still need to stop the requested user if it's a force stop.
- if (force) {
- Slogf.i(TAG,
- "Force stop user " + userId + ". Related users will not be stopped");
- t.traceBegin("stopSingleUserLU-force-" + userId + "-[stopUser]");
- stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback,
- keyEvictedCallback);
- t.traceEnd();
- return USER_OP_SUCCESS;
- }
+ Slogf.e(TAG, "Cannot stop user %d because it is related to user %d. ",
+ userId, relatedUserId);
return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
}
}
+
+ TimingsTraceAndSlog t = new TimingsTraceAndSlog();
if (DEBUG_MU) Slogf.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
for (int userIdToStop : usersToStop) {
t.traceBegin("stopSingleUserLU-" + userIdToStop + "-[stopUser]");
@@ -1304,8 +1318,8 @@
mInjector.activityManagerOnUserStopped(userId);
// Clean up all state and processes associated with the user.
// Kill all the processes for the user.
- t.traceBegin("forceStopUser-" + userId + "-[stopUser]");
- forceStopUser(userId, "finish user");
+ t.traceBegin("stopPackagesOfStoppedUser-" + userId + "-[stopUser]");
+ stopPackagesOfStoppedUser(userId, "finish user");
t.traceEnd();
}
@@ -1516,8 +1530,8 @@
return userIds.toArray();
}
- private void forceStopUser(@UserIdInt int userId, String reason) {
- if (DEBUG_MU) Slogf.i(TAG, "forceStopUser(%d): %s", userId, reason);
+ private void stopPackagesOfStoppedUser(@UserIdInt int userId, String reason) {
+ if (DEBUG_MU) Slogf.i(TAG, "stopPackagesOfStoppedUser(%d): %s", userId, reason);
mInjector.activityManagerForceStopPackage(userId, reason);
if (mInjector.getUserManager().isPreCreated(userId)) {
// Don't fire intent for precreated.
@@ -1566,8 +1580,7 @@
// This is a user to be stopped.
Slogf.i(TAG, "Stopping background guest or ephemeral user " + oldUserId);
synchronized (mLock) {
- stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
- null, null);
+ stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
}
}
}
@@ -2259,8 +2272,7 @@
// If running in background is disabled or mStopUserOnSwitch mode, stop the user.
if (hasRestriction || shouldStopUserOnSwitch()) {
Slogf.i(TAG, "Stopping user %d and its profiles on user switch", oldUserId);
- stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ false,
- null, null);
+ stopUsersLU(oldUserId, /* allowDelayedLocking= */ false, null, null);
return;
}
}
@@ -2275,7 +2287,8 @@
Slogf.i(TAG, "Stopping profile %d on user switch", profileUserId);
synchronized (mLock) {
stopUsersLU(profileUserId,
- /* force= */ true, /* allowDelayedLocking= */ false, null, null);
+ /* stopProfileRegardlessOfParent= */ false,
+ /* allowDelayedLocking= */ false, null, null);
}
}
}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index fd847f1..e1ccf4d 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.am"
+container: "system"
flag {
name: "oomadjuster_correctness_rewrite"
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 656611a..77654d4 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1517,8 +1517,9 @@
sendLMsgNoDelay(MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY, SENDMSG_QUEUE, deviceState);
}
- /*package*/ void postUpdatedAdiDeviceState(AdiDeviceState deviceState) {
- sendLMsgNoDelay(MSG_L_UPDATED_ADI_DEVICE_STATE, SENDMSG_QUEUE, deviceState);
+ /*package*/ void postUpdatedAdiDeviceState(AdiDeviceState deviceState, boolean initSA) {
+ sendILMsgNoDelay(
+ MSG_IL_UPDATED_ADI_DEVICE_STATE, SENDMSG_QUEUE, initSA ? 1 : 0, deviceState);
}
/*package*/ static final class CommunicationDeviceInfo {
@@ -2048,8 +2049,8 @@
}
} break;
- case MSG_L_UPDATED_ADI_DEVICE_STATE:
- mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj);
+ case MSG_IL_UPDATED_ADI_DEVICE_STATE:
+ mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj, msg.arg1 == 1);
break;
default:
@@ -2136,7 +2137,7 @@
private static final int MSG_CHECK_COMMUNICATION_ROUTE_CLIENT_STATE = 56;
private static final int MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES = 57;
private static final int MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY = 58;
- private static final int MSG_L_UPDATED_ADI_DEVICE_STATE = 59;
+ private static final int MSG_IL_UPDATED_ADI_DEVICE_STATE = 59;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ebe9c63..f38b381 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -173,7 +173,7 @@
if (ads.getAudioDeviceCategory() != category && (userDefined
|| category != AUDIO_DEVICE_CATEGORY_UNKNOWN)) {
ads.setAudioDeviceCategory(category);
- mDeviceBroker.postUpdatedAdiDeviceState(ads);
+ mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/);
mDeviceBroker.postPersistAudioDeviceSettings();
}
mDeviceBroker.postSynchronizeAdiDevicesInInventory(ads);
@@ -186,7 +186,7 @@
mDeviceInventory.put(ads.getDeviceId(), ads);
checkDeviceInventorySize_l();
- mDeviceBroker.postUpdatedAdiDeviceState(ads);
+ mDeviceBroker.postUpdatedAdiDeviceState(ads, true /*initSA*/);
mDeviceBroker.postPersistAudioDeviceSettings();
}
}
@@ -216,7 +216,7 @@
checkDeviceInventorySize_l();
}
if (updatedCategory.get()) {
- mDeviceBroker.postUpdatedAdiDeviceState(deviceState);
+ mDeviceBroker.postUpdatedAdiDeviceState(deviceState, false /*initSA*/);
}
mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
}
@@ -318,7 +318,7 @@
}
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
- mDeviceBroker.postUpdatedAdiDeviceState(ads2);
+ mDeviceBroker.postUpdatedAdiDeviceState(ads2, false /*initSA*/);
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"synchronizeBleDeviceInInventory synced device pair ads1="
+ updatedDevice + " ads2=" + ads2).printLog(TAG));
@@ -339,7 +339,7 @@
}
ads2.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
- mDeviceBroker.postUpdatedAdiDeviceState(ads2);
+ mDeviceBroker.postUpdatedAdiDeviceState(ads2, false /*initSA*/);
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"synchronizeBleDeviceInInventory synced device pair ads1="
+ updatedDevice + " peer ads2=" + ads2).printLog(TAG));
@@ -364,7 +364,7 @@
}
ads.setAudioDeviceCategory(updatedDevice.getAudioDeviceCategory());
- mDeviceBroker.postUpdatedAdiDeviceState(ads);
+ mDeviceBroker.postUpdatedAdiDeviceState(ads, false /*initSA*/);
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"synchronizeDeviceProfilesInInventory synced device pair ads1="
+ updatedDevice + " ads2=" + ads).printLog(TAG));
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 40099581..5ba0af4 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -8868,6 +8868,7 @@
boolean changed;
int oldIndex;
final boolean isCurrentDevice;
+ final StringBuilder aliasStreamIndexes = new StringBuilder();
synchronized (mSettingsLock) {
synchronized (VolumeStreamState.class) {
oldIndex = getIndex(device);
@@ -8894,13 +8895,17 @@
(changed || !aliasStreamState.hasIndexForDevice(device))) {
final int scaledIndex =
rescaleIndex(aliasIndex, mStreamType, streamType);
- aliasStreamState.setIndex(scaledIndex, device, caller,
- hasModifyAudioSettings);
+ boolean changedAlias = aliasStreamState.setIndex(scaledIndex, device,
+ caller, hasModifyAudioSettings);
if (isCurrentDevice) {
- aliasStreamState.setIndex(scaledIndex,
+ changedAlias |= aliasStreamState.setIndex(scaledIndex,
getDeviceForStream(streamType), caller,
hasModifyAudioSettings);
}
+ if (changedAlias) {
+ aliasStreamIndexes.append(AudioSystem.streamToString(streamType))
+ .append(":").append((scaledIndex + 5) / 10).append(" ");
+ }
}
}
// Mirror changes in SPEAKER ringtone volume on SCO when
@@ -8940,8 +8945,15 @@
oldIndex);
mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
mStreamVolumeAlias[mStreamType]);
- AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
- mStreamType, mStreamVolumeAlias[mStreamType], index, oldIndex));
+ if (mStreamType == mStreamVolumeAlias[mStreamType]) {
+ String aliasStreamIndexesString = "";
+ if (!aliasStreamIndexes.isEmpty()) {
+ aliasStreamIndexesString =
+ " aliased streams: " + aliasStreamIndexes;
+ }
+ AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
+ mStreamType, aliasStreamIndexesString, index, oldIndex));
+ }
sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions);
}
}
@@ -11284,7 +11296,8 @@
mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState);
mDeviceBroker.postPersistAudioDeviceSettings();
- mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes());
+ mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes(),
+ false /* initState */);
mSoundDoseHelper.setAudioDeviceCategory(addr, internalType,
btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES);
}
@@ -11355,11 +11368,11 @@
/** Update the sound dose and spatializer state based on the new AdiDeviceState. */
@VisibleForTesting(visibility = PACKAGE)
- public void onUpdatedAdiDeviceState(AdiDeviceState deviceState) {
+ public void onUpdatedAdiDeviceState(AdiDeviceState deviceState, boolean initSA) {
if (deviceState == null) {
return;
}
- mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes());
+ mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes(), initSA);
mSoundDoseHelper.setAudioDeviceCategory(deviceState.getDeviceAddress(),
deviceState.getInternalDeviceType(),
deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES);
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 3b1c011..749044e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -151,13 +151,13 @@
static final class VolChangedBroadcastEvent extends EventLogger.Event {
final int mStreamType;
- final int mAliasStreamType;
+ final String mAliasStreamIndexes;
final int mIndex;
final int mOldIndex;
- VolChangedBroadcastEvent(int stream, int alias, int index, int oldIndex) {
+ VolChangedBroadcastEvent(int stream, String aliasIndexes, int index, int oldIndex) {
mStreamType = stream;
- mAliasStreamType = alias;
+ mAliasStreamIndexes = aliasIndexes;
mIndex = index;
mOldIndex = oldIndex;
}
@@ -167,8 +167,8 @@
return new StringBuilder("sending VOLUME_CHANGED stream:")
.append(AudioSystem.streamToString(mStreamType))
.append(" index:").append(mIndex)
- .append(" (was:").append(mOldIndex)
- .append(") alias:").append(AudioSystem.streamToString(mAliasStreamType))
+ .append(" (was:").append(mOldIndex).append(")")
+ .append(mAliasStreamIndexes)
.toString();
}
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 08da32e..28af222 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -235,6 +235,10 @@
public int trackPlayer(PlayerBase.PlayerIdCard pic) {
final int newPiid = AudioSystem.newAudioPlayerId();
if (DEBUG) { Log.v(TAG, "trackPlayer() new piid=" + newPiid); }
+ if (newPiid == PLAYER_PIID_INVALID) {
+ Log.w(TAG, "invalid piid assigned from AudioSystem");
+ return newPiid;
+ }
final AudioPlaybackConfiguration apc =
new AudioPlaybackConfiguration(pic, newPiid,
Binder.getCallingUid(), Binder.getCallingPid());
@@ -365,15 +369,14 @@
sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
- mEventHandler.sendMessage(
- mEventHandler.obtainMessage(MSG_II_UPDATE_PORT_EVENT, eventValue, piid));
+ mPortIdToPiid.put(eventValue, piid);
return;
} else if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
for (Integer uidInteger: mBannedUids) {
if (checkBanPlayer(apc, uidInteger.intValue())) {
// player was banned, do not update its state
sEventLogger.enqueue(new EventLogger.StringEvent(
- "not starting piid:" + piid + " ,is banned"));
+ "not starting piid:" + piid + ", is banned"));
return;
}
}
@@ -429,16 +432,12 @@
synchronized (mPlayerLock) {
int piid = mPortIdToPiid.get(portId, PLAYER_PIID_INVALID);
if (piid == PLAYER_PIID_INVALID) {
- if (DEBUG) {
- Log.v(TAG, "No piid assigned for invalid/internal port id " + portId);
- }
+ Log.w(TAG, "No piid assigned for invalid/internal port id " + portId);
return;
}
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc == null) {
- if (DEBUG) {
- Log.v(TAG, "No AudioPlaybackConfiguration assigned for piid " + piid);
- }
+ Log.w(TAG, "No AudioPlaybackConfiguration assigned for piid " + piid);
return;
}
@@ -470,11 +469,18 @@
public void releasePlayer(int piid, int binderUid) {
if (DEBUG) { Log.v(TAG, "releasePlayer() for piid=" + piid); }
boolean change = false;
+ if (piid == PLAYER_PIID_INVALID) {
+ Log.w(TAG, "Received releasePlayer with invalid piid: " + piid);
+ sEventLogger.enqueue(new EventLogger.StringEvent("releasePlayer with invalid piid:"
+ + piid + ", uid:" + binderUid));
+ return;
+ }
+
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (checkConfigurationCaller(piid, apc, binderUid)) {
sEventLogger.enqueue(new EventLogger.StringEvent(
- "releasing player piid:" + piid));
+ "releasing player piid:" + piid + ", uid:" + binderUid));
mPlayers.remove(new Integer(piid));
mDuckingManager.removeReleased(apc);
mFadeOutManager.removeReleased(apc);
@@ -484,8 +490,10 @@
AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
// remove all port ids mapped to the released player
- mEventHandler.sendMessage(
- mEventHandler.obtainMessage(MSG_I_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
+ int portIdx;
+ while ((portIdx = mPortIdToPiid.indexOfValue(piid)) >= 0) {
+ mPortIdToPiid.removeAt(portIdx);
+ }
if (change && mDoNotLogPiidList.contains(piid)) {
// do not dispatch a change for a "do not log" player
@@ -1609,14 +1617,6 @@
private static final int MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION = 1;
/**
- * assign new port id to piid
- * args:
- * msg.arg1: port id
- * msg.arg2: piid
- */
- private static final int MSG_II_UPDATE_PORT_EVENT = 2;
-
- /**
* event for player getting muted
* args:
* msg.arg1: piid
@@ -1624,14 +1624,7 @@
* msg.obj: extras describing the mute reason
* type: PersistableBundle
*/
- private static final int MSG_IIL_UPDATE_PLAYER_MUTED_EVENT = 3;
-
- /**
- * clear all ports assigned to a given piid
- * args:
- * msg.arg1: the piid
- */
- private static final int MSG_I_CLEAR_PORTS_FOR_PIID = 4;
+ private static final int MSG_IIL_UPDATE_PLAYER_MUTED_EVENT = 2;
/**
* event for player reporting playback format and spatialization status
@@ -1641,7 +1634,7 @@
* msg.obj: extras describing the sample rate, channel mask, spatialized
* type: PersistableBundle
*/
- private static final int MSG_IIL_UPDATE_PLAYER_FORMAT = 5;
+ private static final int MSG_IIL_UPDATE_PLAYER_FORMAT = 3;
private void initEventHandler() {
mEventThread = new HandlerThread(TAG);
@@ -1660,11 +1653,6 @@
mMuteAwaitConnectionTimeoutCb.accept((AudioDeviceAttributes) msg.obj);
break;
- case MSG_II_UPDATE_PORT_EVENT:
- synchronized (mPlayerLock) {
- mPortIdToPiid.put(/*portId*/msg.arg1, /*piid*/msg.arg2);
- }
- break;
case MSG_IIL_UPDATE_PLAYER_MUTED_EVENT:
// TODO: replace PersistableBundle with own struct
PersistableBundle extras = (PersistableBundle) msg.obj;
@@ -1680,10 +1668,7 @@
sEventLogger.enqueue(
new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValue));
- final AudioPlaybackConfiguration apc;
- synchronized (mPlayerLock) {
- apc = mPlayers.get(piid);
- }
+ final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc == null || !apc.handleMutedEvent(eventValue)) {
break; // do not dispatch
}
@@ -1691,21 +1676,6 @@
}
break;
- case MSG_I_CLEAR_PORTS_FOR_PIID:
- int piid = msg.arg1;
- if (piid == AudioPlaybackConfiguration.PLAYER_PIID_INVALID) {
- Log.w(TAG, "Received clear ports with invalid piid");
- break;
- }
-
- synchronized (mPlayerLock) {
- int portIdx;
- while ((portIdx = mPortIdToPiid.indexOfValue(piid)) >= 0) {
- mPortIdToPiid.removeAt(portIdx);
- }
- }
- break;
-
case MSG_IIL_UPDATE_PLAYER_FORMAT:
final PersistableBundle formatExtras = (PersistableBundle) msg.obj;
if (formatExtras == null) {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 3b5fa7f..38fa79f 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -295,11 +295,11 @@
// could have been called another time after boot in case of audioserver restart
addCompatibleAudioDevice(
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
- false /*forceEnable*/);
+ false /*forceEnable*/, false /*forceInit*/);
// not force-enabling as this device might already be in the device list
addCompatibleAudioDevice(
new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
- false /*forceEnable*/);
+ false /*forceEnable*/, false /*forceInit*/);
} catch (RemoteException e) {
resetCapabilities();
} finally {
@@ -526,7 +526,7 @@
}
synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
- addCompatibleAudioDevice(ada, true /*forceEnable*/);
+ addCompatibleAudioDevice(ada, true /*forceEnable*/, false /*forceInit*/);
}
/**
@@ -540,7 +540,7 @@
*/
@GuardedBy("this")
private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada,
- boolean forceEnable) {
+ boolean forceEnable, boolean forceInit) {
if (!isDeviceCompatibleWithSpatializationModes(ada)) {
return;
}
@@ -548,6 +548,9 @@
final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada);
AdiDeviceState updatedDevice = null; // non-null on update.
if (deviceState != null) {
+ if (forceInit) {
+ initSAState(deviceState);
+ }
if (forceEnable && !deviceState.isSAEnabled()) {
updatedDevice = deviceState;
updatedDevice.setSAEnabled(true);
@@ -756,10 +759,10 @@
}
}
- synchronized void refreshDevice(@NonNull AudioDeviceAttributes ada) {
+ synchronized void refreshDevice(@NonNull AudioDeviceAttributes ada, boolean initState) {
final AdiDeviceState deviceState = findSACompatibleDeviceStateForAudioDeviceAttributes(ada);
if (isAvailableForAdiDeviceState(deviceState)) {
- addCompatibleAudioDevice(ada, /*forceEnable=*/deviceState.isSAEnabled());
+ addCompatibleAudioDevice(ada, /*forceEnable=*/deviceState.isSAEnabled(), initState);
setHeadTrackerEnabled(deviceState.isHeadTrackerEnabled(), ada);
} else {
removeCompatibleAudioDevice(ada);
diff --git a/services/core/java/com/android/server/biometrics/Android.bp b/services/core/java/com/android/server/biometrics/Android.bp
index 6cbe4ad..ed5de030 100644
--- a/services/core/java/com/android/server/biometrics/Android.bp
+++ b/services/core/java/com/android/server/biometrics/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "biometrics_flags",
package: "com.android.server.biometrics",
+ container: "system",
srcs: [
"biometrics.aconfig",
],
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 3d4801b..c03b3b8 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -56,7 +56,7 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.IBinder;
import android.os.RemoteException;
-import android.security.KeyStore;
+import android.security.KeyStoreAuthorization;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -111,7 +111,7 @@
@NonNull private final BiometricContext mBiometricContext;
private final IStatusBarService mStatusBarService;
@VisibleForTesting final IBiometricSysuiReceiver mSysuiReceiver;
- private final KeyStore mKeyStore;
+ private final KeyStoreAuthorization mKeyStoreAuthorization;
private final Random mRandom;
private final ClientDeathReceiver mClientDeathReceiver;
final PreAuthInfo mPreAuthInfo;
@@ -158,7 +158,7 @@
@NonNull BiometricContext biometricContext,
@NonNull IStatusBarService statusBarService,
@NonNull IBiometricSysuiReceiver sysuiReceiver,
- @NonNull KeyStore keystore,
+ @NonNull KeyStoreAuthorization keyStoreAuthorization,
@NonNull Random random,
@NonNull ClientDeathReceiver clientDeathReceiver,
@NonNull PreAuthInfo preAuthInfo,
@@ -172,8 +172,8 @@
@NonNull PromptInfo promptInfo,
boolean debugEnabled,
@NonNull List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties) {
- this(context, biometricContext, statusBarService, sysuiReceiver, keystore, random,
- clientDeathReceiver, preAuthInfo, token, requestId, operationId, userId,
+ this(context, biometricContext, statusBarService, sysuiReceiver, keyStoreAuthorization,
+ random, clientDeathReceiver, preAuthInfo, token, requestId, operationId, userId,
sensorReceiver, clientReceiver, opPackageName, promptInfo, debugEnabled,
fingerprintSensorProperties, BiometricFrameworkStatsLogger.getInstance());
}
@@ -183,7 +183,7 @@
@NonNull BiometricContext biometricContext,
@NonNull IStatusBarService statusBarService,
@NonNull IBiometricSysuiReceiver sysuiReceiver,
- @NonNull KeyStore keystore,
+ @NonNull KeyStoreAuthorization keyStoreAuthorization,
@NonNull Random random,
@NonNull ClientDeathReceiver clientDeathReceiver,
@NonNull PreAuthInfo preAuthInfo,
@@ -203,7 +203,7 @@
mBiometricContext = biometricContext;
mStatusBarService = statusBarService;
mSysuiReceiver = sysuiReceiver;
- mKeyStore = keystore;
+ mKeyStoreAuthorization = keyStoreAuthorization;
mRandom = random;
mClientDeathReceiver = clientDeathReceiver;
mPreAuthInfo = preAuthInfo;
@@ -814,14 +814,14 @@
switch (reason) {
case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED:
if (credentialAttestation != null) {
- mKeyStore.addAuthToken(credentialAttestation);
+ mKeyStoreAuthorization.addAuthToken(credentialAttestation);
} else {
Slog.e(TAG, "credentialAttestation is null");
}
case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED:
case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED:
if (mTokenEscrow != null) {
- final int result = mKeyStore.addAuthToken(mTokenEscrow);
+ final int result = mKeyStoreAuthorization.addAuthToken(mTokenEscrow);
Slog.d(TAG, "addAuthToken: " + result);
} else {
Slog.e(TAG, "mTokenEscrow is null");
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 894b4d5..3737d6f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -65,15 +65,11 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.security.Authorization;
import android.security.GateKeeper;
-import android.security.KeyStore;
-import android.security.authorization.IKeystoreAuthorization;
-import android.security.authorization.ResponseCode;
+import android.security.KeyStoreAuthorization;
import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -123,11 +119,9 @@
@VisibleForTesting
IStatusBarService mStatusBarService;
@VisibleForTesting
- KeyStore mKeyStore;
- @VisibleForTesting
ITrustManager mTrustManager;
@VisibleForTesting
- IKeystoreAuthorization mKeystoreAuthorization;
+ KeyStoreAuthorization mKeyStoreAuthorization;
@VisibleForTesting
IGateKeeperService mGateKeeper;
@@ -674,19 +668,7 @@
int[] authTypesArray = hardwareAuthenticators.stream()
.mapToInt(Integer::intValue)
.toArray();
- try {
- return mKeystoreAuthorization.getLastAuthTime(secureUserId, authTypesArray);
- } catch (RemoteException e) {
- Slog.w(TAG, "Error getting last auth time: " + e);
- return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
- } catch (ServiceSpecificException e) {
- // This is returned when the feature flag test fails in keystore2
- if (e.errorCode == ResponseCode.PERMISSION_DENIED) {
- throw new UnsupportedOperationException();
- }
-
- return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION;
- }
+ return mKeyStoreAuthorization.getLastAuthTime(secureUserId, authTypesArray);
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@@ -1011,8 +993,8 @@
return ActivityManager.getService();
}
- public IKeystoreAuthorization getKeystoreAuthorizationService() {
- return Authorization.getService();
+ public KeyStoreAuthorization getKeyStoreAuthorization() {
+ return KeyStoreAuthorization.getInstance();
}
public IGateKeeperService getGateKeeperService() {
@@ -1036,10 +1018,6 @@
return new SettingObserver(context, handler, callbacks);
}
- public KeyStore getKeyStore() {
- return KeyStore.getInstance();
- }
-
/**
* Allows to enable/disable debug logs.
*/
@@ -1138,7 +1116,7 @@
mBiometricContext = injector.getBiometricContext(context);
mUserManager = injector.getUserManager(context);
mBiometricCameraManager = injector.getBiometricCameraManager(context);
- mKeystoreAuthorization = injector.getKeystoreAuthorizationService();
+ mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
mGateKeeper = injector.getGateKeeperService();
mBiometricNotificationLogger = injector.getNotificationLogger();
@@ -1159,7 +1137,6 @@
@Override
public void onStart() {
- mKeyStore = mInjector.getKeyStore();
mStatusBarService = mInjector.getStatusBarService();
mTrustManager = mInjector.getTrustManager();
mInjector.publishBinderService(this, mImpl);
@@ -1481,7 +1458,7 @@
final boolean debugEnabled = mInjector.isDebugEnabled(getContext(), userId);
mAuthSession = new AuthSession(getContext(), mBiometricContext, mStatusBarService,
- createSysuiReceiver(requestId), mKeyStore, mRandom,
+ createSysuiReceiver(requestId), mKeyStoreAuthorization, mRandom,
createClientDeathReceiver(requestId), preAuthInfo, token, requestId,
operationId, userId, createBiometricSensorReceiver(requestId), receiver,
opPackageName, promptInfo, debugEnabled,
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index b12d831..7a9491e 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.biometrics"
+container: "system"
flag {
name: "face_vhal_feature"
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 62c21cf..4fa8741 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -30,7 +30,7 @@
import android.hardware.biometrics.BiometricRequestConstants;
import android.os.IBinder;
import android.os.RemoteException;
-import android.security.KeyStore;
+import android.security.KeyStoreAuthorization;
import android.util.EventLog;
import android.util.Slog;
@@ -255,7 +255,7 @@
// For BP, BiometricService will add the authToken to Keystore.
if (!isBiometricPrompt() && mIsStrongBiometric) {
- final int result = KeyStore.getInstance().addAuthToken(byteToken);
+ final int result = KeyStoreAuthorization.getInstance().addAuthToken(byteToken);
if (result != 0) {
Slog.d(TAG, "Error adding auth token : " + result);
} else {
diff --git a/services/core/java/com/android/server/connectivity/Android.bp b/services/core/java/com/android/server/connectivity/Android.bp
new file mode 100644
index 0000000..a374ec2
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+ name: "connectivity_flags",
+ package: "com.android.server.connectivity",
+ srcs: ["flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "connectivity_flags_lib",
+ aconfig_declarations: "connectivity_flags",
+}
diff --git a/services/core/java/com/android/server/connectivity/flags.aconfig b/services/core/java/com/android/server/connectivity/flags.aconfig
new file mode 100644
index 0000000..32593d4
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.connectivity"
+
+flag {
+ name: "replace_vpn_profile_store"
+ namespace: "android_core_networking"
+ description: "This flag controls the usage of VpnBlobStore to replace LegacyVpnProfileStore."
+ bug: "307903113"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 1dfe037..182b05a 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -82,11 +82,8 @@
private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular";
// Min and max strengths for even dimmer feature.
private static final float EVEN_DIMMER_MIN_STRENGTH = 0.0f;
- private static final float EVEN_DIMMER_MAX_STRENGTH = 70.0f; // not too dim yet.
+ private static final float EVEN_DIMMER_MAX_STRENGTH = 90.0f;
private static final float BRIGHTNESS_MIN = 0.0f;
- // The brightness at which we start using color matrices rather than backlight,
- // to dim the display
- private static final float BACKLIGHT_COLOR_TRANSITION_POINT = 0.1f;
private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
@@ -995,9 +992,12 @@
private void applyColorMatrixBasedDimming(float brightnessState) {
int strength = (int) (MathUtils.constrainedMap(
- EVEN_DIMMER_MAX_STRENGTH, EVEN_DIMMER_MIN_STRENGTH, // to this range
- BRIGHTNESS_MIN, BACKLIGHT_COLOR_TRANSITION_POINT, // from this range
- brightnessState) + 0.5); // map this (+ rounded up)
+ // to this range:
+ EVEN_DIMMER_MAX_STRENGTH, EVEN_DIMMER_MIN_STRENGTH,
+ // from this range:
+ BRIGHTNESS_MIN, mDisplayDeviceConfig.getEvenDimmerTransitionPoint(),
+ // map this (+ rounded up):
+ brightnessState) + 0.5);
if (mEvenDimmerStrength < 0 // uninitialised
|| MathUtils.abs(mEvenDimmerStrength - strength) > 1
diff --git a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
index 5556365..7e2e10a 100644
--- a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
@@ -66,6 +66,10 @@
* Spline, mapping between backlight and brightness
*/
public final Spline mBacklightToBrightness;
+
+ /**
+ * Spline, mapping the minimum nits for each lux condition.
+ */
public final Spline mMinLuxToNits;
@VisibleForTesting
@@ -114,34 +118,35 @@
return null;
}
- List<Float> nitsList = lbm.getNits();
- List<Float> backlightList = lbm.getBacklight();
- List<Float> brightnessList = lbm.getBrightness();
- float transitionPoints = lbm.getTransitionPoint().floatValue();
+ ComprehensiveBrightnessMap map = lbm.getBrightnessMapping();
+ if (map == null) {
+ return null;
+ }
+ String interpolation = map.getInterpolation();
- if (nitsList.isEmpty()
- || backlightList.size() != brightnessList.size()
- || backlightList.size() != nitsList.size()) {
- Slog.e(TAG, "Invalid even dimmer array lengths");
+ List<BrightnessPoint> brightnessPoints = map.getBrightnessPoint();
+ if (brightnessPoints.isEmpty()) {
return null;
}
- float[] nits = new float[nitsList.size()];
- float[] backlight = new float[nitsList.size()];
- float[] brightness = new float[nitsList.size()];
+ float[] nits = new float[brightnessPoints.size()];
+ float[] backlight = new float[brightnessPoints.size()];
+ float[] brightness = new float[brightnessPoints.size()];
- for (int i = 0; i < nitsList.size(); i++) {
- nits[i] = nitsList.get(i);
- backlight[i] = backlightList.get(i);
- brightness[i] = brightnessList.get(i);
+ for (int i = 0; i < brightnessPoints.size(); i++) {
+ BrightnessPoint val = brightnessPoints.get(i);
+ nits[i] = val.getNits().floatValue();
+ backlight[i] = val.getBacklight().floatValue();
+ brightness[i] = val.getBrightness().floatValue();
}
- final NitsMap map = lbm.getLuxToMinimumNitsMap();
- if (map == null) {
+ float transitionPoint = lbm.getTransitionPoint().floatValue();
+ final NitsMap minimumNitsMap = lbm.getLuxToMinimumNitsMap();
+ if (minimumNitsMap == null) {
Slog.e(TAG, "Invalid min lux to nits mapping");
return null;
}
- final List<Point> points = map.getPoint();
+ final List<Point> points = minimumNitsMap.getPoint();
final int size = points.size();
float[] minLux = new float[size];
@@ -164,7 +169,18 @@
++i;
}
- return new EvenDimmerBrightnessData(transitionPoints, nits, backlight, brightness,
+ // Explicitly choose linear interpolation.
+ if ("linear".equals(interpolation)) {
+ return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
+ new Spline.LinearSpline(backlight, nits),
+ new Spline.LinearSpline(nits, backlight),
+ new Spline.LinearSpline(brightness, backlight),
+ new Spline.LinearSpline(backlight, brightness),
+ new Spline.LinearSpline(minLux, minNits)
+ );
+ }
+
+ return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
Spline.createSpline(backlight, nits),
Spline.createSpline(nits, backlight),
Spline.createSpline(brightness, backlight),
diff --git a/services/core/java/com/android/server/feature/Android.bp b/services/core/java/com/android/server/feature/Android.bp
index 067288d..b0fbab6 100644
--- a/services/core/java/com/android/server/feature/Android.bp
+++ b/services/core/java/com/android/server/feature/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "dropbox_flags",
package: "com.android.server.feature.flags",
+ container: "system",
srcs: [
"dropbox_flags.aconfig",
],
diff --git a/services/core/java/com/android/server/feature/dropbox_flags.aconfig b/services/core/java/com/android/server/feature/dropbox_flags.aconfig
index 14e964b..98978f0 100644
--- a/services/core/java/com/android/server/feature/dropbox_flags.aconfig
+++ b/services/core/java/com/android/server/feature/dropbox_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.feature.flags"
+container: "system"
flag{
name: "enable_read_dropbox_permission"
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 7b844a0..f383679 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -165,7 +165,11 @@
@Override
public void onBootPhase(int phase) {
- if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ final int latestFontLoadBootPhase =
+ (Flags.completeFontLoadInSystemServicesReady())
+ ? SystemService.PHASE_SYSTEM_SERVICES_READY
+ : SystemService.PHASE_ACTIVITY_MANAGER_READY;
+ if (phase == latestFontLoadBootPhase) {
// Wait for FontManagerService to start since it will be needed after this point.
mServiceStarted.join();
}
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 9d04682..ea240c7 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -196,7 +196,12 @@
File signatureFile = new File(dir, FONT_SIGNATURE_FILE);
if (!signatureFile.exists()) {
Slog.i(TAG, "The signature file is missing.");
- return;
+ if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+ return;
+ } else {
+ FileUtils.deleteContentsAndDir(dir);
+ continue;
+ }
}
byte[] signature;
try {
@@ -221,33 +226,39 @@
FontFileInfo fontFileInfo = validateFontFile(fontFile, signature);
if (fontConfig == null) {
- // Use preinstalled font config for checking revision number.
- fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+ if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+ // Use preinstalled font config for checking revision number.
+ fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+ } else {
+ fontConfig = getSystemFontConfig();
+ }
}
addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */);
}
- // Treat as error if post script name of font family was not installed.
- for (int i = 0; i < config.fontFamilies.size(); ++i) {
- FontUpdateRequest.Family family = config.fontFamilies.get(i);
- for (int j = 0; j < family.getFonts().size(); ++j) {
- FontUpdateRequest.Font font = family.getFonts().get(j);
- if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
- continue;
- }
+ if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+ // Treat as error if post script name of font family was not installed.
+ for (int i = 0; i < config.fontFamilies.size(); ++i) {
+ FontUpdateRequest.Family family = config.fontFamilies.get(i);
+ for (int j = 0; j < family.getFonts().size(); ++j) {
+ FontUpdateRequest.Font font = family.getFonts().get(j);
+ if (mFontFileInfoMap.containsKey(font.getPostScriptName())) {
+ continue;
+ }
- if (fontConfig == null) {
- fontConfig = mConfigSupplier.apply(Collections.emptyMap());
- }
+ if (fontConfig == null) {
+ fontConfig = mConfigSupplier.apply(Collections.emptyMap());
+ }
- if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
- continue;
- }
+ if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) {
+ continue;
+ }
- Slog.e(TAG, "Unknown font that has PostScript name "
- + font.getPostScriptName() + " is requested in FontFamily "
- + family.getName());
- return;
+ Slog.e(TAG, "Unknown font that has PostScript name "
+ + font.getPostScriptName() + " is requested in FontFamily "
+ + family.getName());
+ return;
+ }
}
}
@@ -262,7 +273,9 @@
mFontFileInfoMap.clear();
mLastModifiedMillis = 0;
FileUtils.deleteContents(mFilesDir);
- mConfigFile.delete();
+ if (com.android.text.flags.Flags.fixFontUpdateFailure()) {
+ mConfigFile.delete();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 77119d5..f32c11d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1197,54 +1197,11 @@
}
@Override // Binder call
- public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
- final InputDeviceIdentifier identifier) {
- return mKeyboardLayoutManager.getKeyboardLayoutsForInputDevice(identifier);
- }
-
- @Override // Binder call
public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
return mKeyboardLayoutManager.getKeyboardLayout(keyboardLayoutDescriptor);
}
@Override // Binder call
- public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
- return mKeyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(identifier);
- }
-
- @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
- @Override // Binder call
- public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- super.setCurrentKeyboardLayoutForInputDevice_enforcePermission();
- mKeyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
-
- @Override // Binder call
- public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
- return mKeyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(identifier);
- }
-
- @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
- @Override // Binder call
- public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- super.addKeyboardLayoutForInputDevice_enforcePermission();
- mKeyboardLayoutManager.addKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
-
- @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
- @Override // Binder call
- public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- super.removeKeyboardLayoutForInputDevice_enforcePermission();
- mKeyboardLayoutManager.removeKeyboardLayoutForInputDevice(identifier,
- keyboardLayoutDescriptor);
- }
-
- @Override // Binder call
public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
InputDeviceIdentifier identifier, @UserIdInt int userId,
@NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
@@ -1270,11 +1227,6 @@
imeInfo, imeSubtype);
}
-
- public void switchKeyboardLayout(int deviceId, int direction) {
- mKeyboardLayoutManager.switchKeyboardLayout(deviceId, direction);
- }
-
public void setFocusedApplication(int displayId, InputApplicationHandle application) {
mNative.setFocusedApplication(displayId, application);
}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 6610081..9ba647f 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -60,7 +60,6 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -69,7 +68,6 @@
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
-import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -96,7 +94,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.stream.Stream;
/**
* A component of {@link InputManagerService} responsible for managing Physical Keyboard layouts.
@@ -112,9 +109,8 @@
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
- private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
- private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
- private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
+ private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 2;
+ private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 3;
private final Context mContext;
private final NativeInputManagerService mNative;
@@ -126,13 +122,14 @@
// Connected keyboards with associated keyboard layouts (either auto-detected or manually
// selected layout).
private final SparseArray<KeyboardConfiguration> mConfiguredKeyboards = new SparseArray<>();
- private Toast mSwitchedKeyboardLayoutToast;
// This cache stores "best-matched" layouts so that we don't need to run the matching
// algorithm repeatedly.
@GuardedBy("mKeyboardLayoutCache")
private final Map<String, KeyboardLayoutSelectionResult> mKeyboardLayoutCache =
new ArrayMap<>();
+
+ private HashSet<String> mAvailableLayouts = new HashSet<>();
private final Object mImeInfoLock = new Object();
@Nullable
@GuardedBy("mImeInfoLock")
@@ -206,68 +203,51 @@
}
boolean needToShowNotification = false;
- if (!useNewSettingsUi()) {
- synchronized (mDataStore) {
- String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
- if (layout == null) {
- layout = getDefaultKeyboardLayout(inputDevice);
- if (layout != null) {
- setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
- }
- }
- if (layout == null) {
- // In old settings show notification always until user manually selects a
- // layout in the settings.
+ Set<String> selectedLayouts = new HashSet<>();
+ List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
+ List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
+ boolean hasMissingLayout = false;
+ for (ImeInfo imeInfo : imeInfoList) {
+ // Check if the layout has been previously configured
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
+ keyboardIdentifier, imeInfo);
+ if (result.getLayoutDescriptor() != null) {
+ selectedLayouts.add(result.getLayoutDescriptor());
+ } else {
+ hasMissingLayout = true;
+ }
+ resultList.add(result);
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Layouts selected for input device: " + keyboardIdentifier
+ + " -> selectedLayouts: " + selectedLayouts);
+ }
+
+ // If even one layout not configured properly, we need to ask user to configure
+ // the keyboard properly from the Settings.
+ if (hasMissingLayout) {
+ selectedLayouts.clear();
+ }
+
+ config.setConfiguredLayouts(selectedLayouts);
+
+ synchronized (mDataStore) {
+ try {
+ final String key = keyboardIdentifier.toString();
+ if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
+ // Need to show the notification only if layout selection changed
+ // from the previous configuration
needToShowNotification = true;
}
- }
- } else {
- Set<String> selectedLayouts = new HashSet<>();
- List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
- List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
- boolean hasMissingLayout = false;
- for (ImeInfo imeInfo : imeInfoList) {
- // Check if the layout has been previously configured
- KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
- keyboardIdentifier, imeInfo);
- boolean noLayoutFound = result.getLayoutDescriptor() == null;
- if (!noLayoutFound) {
- selectedLayouts.add(result.getLayoutDescriptor());
+
+ if (shouldLogConfiguration) {
+ logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
+ !mDataStore.hasInputDeviceEntry(key));
}
- resultList.add(result);
- hasMissingLayout |= noLayoutFound;
- }
-
- if (DEBUG) {
- Slog.d(TAG,
- "Layouts selected for input device: " + keyboardIdentifier
- + " -> selectedLayouts: " + selectedLayouts);
- }
-
- // If even one layout not configured properly, we need to ask user to configure
- // the keyboard properly from the Settings.
- if (hasMissingLayout) {
- selectedLayouts.clear();
- }
-
- config.setConfiguredLayouts(selectedLayouts);
-
- synchronized (mDataStore) {
- try {
- final String key = keyboardIdentifier.toString();
- if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
- // Need to show the notification only if layout selection changed
- // from the previous configuration
- needToShowNotification = true;
- }
-
- if (shouldLogConfiguration) {
- logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
- !mDataStore.hasInputDeviceEntry(key));
- }
- } finally {
- mDataStore.saveIfNeeded();
- }
+ } finally {
+ mDataStore.saveIfNeeded();
}
}
if (needToShowNotification) {
@@ -275,63 +255,6 @@
}
}
- private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
- final Locale systemLocale = mContext.getResources().getConfiguration().locale;
- // If our locale doesn't have a language for some reason, then we don't really have a
- // reasonable default.
- if (TextUtils.isEmpty(systemLocale.getLanguage())) {
- return null;
- }
- final List<KeyboardLayout> layouts = new ArrayList<>();
- visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> {
- // Only select a default when we know the layout is appropriate. For now, this
- // means it's a custom layout for a specific keyboard.
- if (layout.getVendorId() != inputDevice.getVendorId()
- || layout.getProductId() != inputDevice.getProductId()) {
- return;
- }
- final LocaleList locales = layout.getLocales();
- for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
- final Locale locale = locales.get(localeIndex);
- if (locale != null && isCompatibleLocale(systemLocale, locale)) {
- layouts.add(layout);
- break;
- }
- }
- });
-
- if (layouts.isEmpty()) {
- return null;
- }
-
- // First sort so that ones with higher priority are listed at the top
- Collections.sort(layouts);
- // Next we want to try to find an exact match of language, country and variant.
- for (KeyboardLayout layout : layouts) {
- final LocaleList locales = layout.getLocales();
- for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
- final Locale locale = locales.get(localeIndex);
- if (locale != null && locale.getCountry().equals(systemLocale.getCountry())
- && locale.getVariant().equals(systemLocale.getVariant())) {
- return layout.getDescriptor();
- }
- }
- }
- // Then try an exact match of language and country
- for (KeyboardLayout layout : layouts) {
- final LocaleList locales = layout.getLocales();
- for (int localeIndex = 0; localeIndex < locales.size(); ++localeIndex) {
- final Locale locale = locales.get(localeIndex);
- if (locale != null && locale.getCountry().equals(systemLocale.getCountry())) {
- return layout.getDescriptor();
- }
- }
- }
-
- // Give up and just use the highest priority layout with matching language
- return layouts.get(0).getDescriptor();
- }
-
private static boolean isCompatibleLocale(Locale systemLocale, Locale keyboardLocale) {
// Different languages are never compatible
if (!systemLocale.getLanguage().equals(keyboardLocale.getLanguage())) {
@@ -343,11 +266,19 @@
|| systemLocale.getCountry().equals(keyboardLocale.getCountry());
}
+ @MainThread
private void updateKeyboardLayouts() {
// Scan all input devices state for keyboard layouts that have been uninstalled.
- final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
+ final HashSet<String> availableKeyboardLayouts = new HashSet<>();
visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) ->
availableKeyboardLayouts.add(layout.getDescriptor()));
+
+ // If available layouts don't change, there is no need to reload layouts.
+ if (mAvailableLayouts.equals(availableKeyboardLayouts)) {
+ return;
+ }
+ mAvailableLayouts = availableKeyboardLayouts;
+
synchronized (mDataStore) {
try {
mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
@@ -374,53 +305,6 @@
}
@AnyThread
- public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
- final InputDeviceIdentifier identifier) {
- if (useNewSettingsUi()) {
- // Provide all supported keyboard layouts since Ime info is not provided
- return getKeyboardLayouts();
- }
- final String[] enabledLayoutDescriptors =
- getEnabledKeyboardLayoutsForInputDevice(identifier);
- final ArrayList<KeyboardLayout> enabledLayouts =
- new ArrayList<>(enabledLayoutDescriptors.length);
- final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
- visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
- boolean mHasSeenDeviceSpecificLayout;
-
- @Override
- public void visitKeyboardLayout(Resources resources,
- int keyboardLayoutResId, KeyboardLayout layout) {
- // First check if it's enabled. If the keyboard layout is enabled then we always
- // want to return it as a possible layout for the device.
- for (String s : enabledLayoutDescriptors) {
- if (s != null && s.equals(layout.getDescriptor())) {
- enabledLayouts.add(layout);
- return;
- }
- }
- // Next find any potential layouts that aren't yet enabled for the device. For
- // devices that have special layouts we assume there's a reason that the generic
- // layouts don't work for them so we don't want to return them since it's likely
- // to result in a poor user experience.
- if (layout.getVendorId() == identifier.getVendorId()
- && layout.getProductId() == identifier.getProductId()) {
- if (!mHasSeenDeviceSpecificLayout) {
- mHasSeenDeviceSpecificLayout = true;
- potentialLayouts.clear();
- }
- potentialLayouts.add(layout);
- } else if (layout.getVendorId() == -1 && layout.getProductId() == -1
- && !mHasSeenDeviceSpecificLayout) {
- potentialLayouts.add(layout);
- }
- }
- });
- return Stream.concat(enabledLayouts.stream(), potentialLayouts.stream()).toArray(
- KeyboardLayout[]::new);
- }
-
- @AnyThread
@Nullable
public KeyboardLayout getKeyboardLayout(@NonNull String keyboardLayoutDescriptor) {
Objects.requireNonNull(keyboardLayoutDescriptor,
@@ -580,195 +464,16 @@
return LocaleList.forLanguageTags(languageTags.replace('|', ','));
}
- @AnyThread
- @Nullable
- public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "getCurrentKeyboardLayoutForInputDevice API not supported");
- return null;
- }
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- String layout;
- // try loading it using the layout descriptor if we have it
- layout = mDataStore.getCurrentKeyboardLayout(key);
- if (layout == null && !key.equals(identifier.getDescriptor())) {
- // if it doesn't exist fall back to the device descriptor
- layout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
- }
- if (DEBUG) {
- Slog.d(TAG, "getCurrentKeyboardLayoutForInputDevice() "
- + identifier.toString() + ": " + layout);
- }
- return layout;
- }
- }
-
- @AnyThread
- public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "setCurrentKeyboardLayoutForInputDevice API not supported");
- return;
- }
-
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- try {
- if (mDataStore.setCurrentKeyboardLayout(key, keyboardLayoutDescriptor)) {
- if (DEBUG) {
- Slog.d(TAG, "setCurrentKeyboardLayoutForInputDevice() " + identifier
- + " key: " + key
- + " keyboardLayoutDescriptor: " + keyboardLayoutDescriptor);
- }
- mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
- }
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
- }
-
- @AnyThread
- public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported");
- return new String[0];
- }
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- String[] layouts = mDataStore.getKeyboardLayouts(key);
- if ((layouts == null || layouts.length == 0)
- && !key.equals(identifier.getDescriptor())) {
- layouts = mDataStore.getKeyboardLayouts(identifier.getDescriptor());
- }
- return layouts;
- }
- }
-
- @AnyThread
- public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "addKeyboardLayoutForInputDevice API not supported");
- return;
- }
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
-
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- try {
- String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
- if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
- oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
- }
- if (mDataStore.addKeyboardLayout(key, keyboardLayoutDescriptor)
- && !Objects.equals(oldLayout,
- mDataStore.getCurrentKeyboardLayout(key))) {
- mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
- }
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
- }
-
- @AnyThread
- public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- String keyboardLayoutDescriptor) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "removeKeyboardLayoutForInputDevice API not supported");
- return;
- }
- Objects.requireNonNull(keyboardLayoutDescriptor,
- "keyboardLayoutDescriptor must not be null");
-
- String key = new KeyboardIdentifier(identifier).toString();
- synchronized (mDataStore) {
- try {
- String oldLayout = mDataStore.getCurrentKeyboardLayout(key);
- if (oldLayout == null && !key.equals(identifier.getDescriptor())) {
- oldLayout = mDataStore.getCurrentKeyboardLayout(identifier.getDescriptor());
- }
- boolean removed = mDataStore.removeKeyboardLayout(key, keyboardLayoutDescriptor);
- if (!key.equals(identifier.getDescriptor())) {
- // We need to remove from both places to ensure it is gone
- removed |= mDataStore.removeKeyboardLayout(identifier.getDescriptor(),
- keyboardLayoutDescriptor);
- }
- if (removed && !Objects.equals(oldLayout,
- mDataStore.getCurrentKeyboardLayout(key))) {
- mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
- }
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
- }
-
- @AnyThread
- public void switchKeyboardLayout(int deviceId, int direction) {
- if (useNewSettingsUi()) {
- Slog.e(TAG, "switchKeyboardLayout API not supported");
- return;
- }
- mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
- }
-
- @MainThread
- private void handleSwitchKeyboardLayout(int deviceId, int direction) {
- final InputDevice device = getInputDevice(deviceId);
- if (device != null) {
- final boolean changed;
- final String keyboardLayoutDescriptor;
-
- String key = new KeyboardIdentifier(device.getIdentifier()).toString();
- synchronized (mDataStore) {
- try {
- changed = mDataStore.switchKeyboardLayout(key, direction);
- keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
- key);
- } finally {
- mDataStore.saveIfNeeded();
- }
- }
-
- if (changed) {
- if (mSwitchedKeyboardLayoutToast != null) {
- mSwitchedKeyboardLayoutToast.cancel();
- mSwitchedKeyboardLayoutToast = null;
- }
- if (keyboardLayoutDescriptor != null) {
- KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
- if (keyboardLayout != null) {
- mSwitchedKeyboardLayoutToast = Toast.makeText(
- mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
- mSwitchedKeyboardLayoutToast.show();
- }
- }
-
- reloadKeyboardLayouts();
- }
- }
- }
-
@Nullable
@AnyThread
public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier, String languageTag,
String layoutType) {
String keyboardLayoutDescriptor;
- if (useNewSettingsUi()) {
- synchronized (mImeInfoLock) {
- KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
- new KeyboardIdentifier(identifier, languageTag, layoutType),
- mCurrentImeInfo);
- keyboardLayoutDescriptor = result.getLayoutDescriptor();
- }
- } else {
- keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
+ synchronized (mImeInfoLock) {
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
+ new KeyboardIdentifier(identifier, languageTag, layoutType),
+ mCurrentImeInfo);
+ keyboardLayoutDescriptor = result.getLayoutDescriptor();
}
if (keyboardLayoutDescriptor == null) {
return null;
@@ -797,10 +502,6 @@
public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
InputDeviceIdentifier identifier, @UserIdInt int userId,
@NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported");
- return FAILED;
- }
InputDevice inputDevice = getInputDevice(identifier);
if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
return FAILED;
@@ -820,10 +521,6 @@
@UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@Nullable InputMethodSubtype imeSubtype,
String keyboardLayoutDescriptor) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "setKeyboardLayoutForInputDevice() API not supported");
- return;
- }
Objects.requireNonNull(keyboardLayoutDescriptor,
"keyboardLayoutDescriptor must not be null");
InputDevice inputDevice = getInputDevice(identifier);
@@ -854,10 +551,6 @@
public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
@UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@Nullable InputMethodSubtype imeSubtype) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "getKeyboardLayoutListForInputDevice() API not supported");
- return new KeyboardLayout[0];
- }
InputDevice inputDevice = getInputDevice(identifier);
if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
return new KeyboardLayout[0];
@@ -923,10 +616,6 @@
public void onInputMethodSubtypeChanged(@UserIdInt int userId,
@Nullable InputMethodSubtypeHandle subtypeHandle,
@Nullable InputMethodSubtype subtype) {
- if (!useNewSettingsUi()) {
- Slog.e(TAG, "onInputMethodSubtypeChanged() API not supported");
- return;
- }
if (subtypeHandle == null) {
if (DEBUG) {
Slog.d(TAG, "No InputMethod is running, ignoring change");
@@ -1289,9 +978,6 @@
onInputDeviceAdded(deviceId);
}
return true;
- case MSG_SWITCH_KEYBOARD_LAYOUT:
- handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
- return true;
case MSG_RELOAD_KEYBOARD_LAYOUTS:
reloadKeyboardLayouts();
return true;
@@ -1303,10 +989,6 @@
}
}
- private boolean useNewSettingsUi() {
- return FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI);
- }
-
@Nullable
private InputDevice getInputDevice(int deviceId) {
InputManager inputManager = mContext.getSystemService(InputManager.class);
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 31083fd..7859253 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -27,7 +27,6 @@
import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -42,7 +41,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -74,7 +72,7 @@
new HashMap<String, InputDeviceState>();
// The interface for methods which should be replaced by the test harness.
- private Injector mInjector;
+ private final Injector mInjector;
// True if the data has been loaded.
private boolean mLoaded;
@@ -83,7 +81,7 @@
private boolean mDirty;
// Storing key remapping
- private Map<Integer, Integer> mKeyRemapping = new HashMap<>();
+ private final Map<Integer, Integer> mKeyRemapping = new HashMap<>();
public PersistentDataStore() {
this(new Injector());
@@ -130,22 +128,6 @@
}
@Nullable
- public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
- return state != null ? state.getCurrentKeyboardLayout() : null;
- }
-
- public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
- InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
- if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
- setDirty();
- return true;
- }
- return false;
- }
-
- @Nullable
public String getKeyboardLayout(String inputDeviceDescriptor, String key) {
InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
return state != null ? state.getKeyboardLayout(key) : null;
@@ -171,43 +153,6 @@
return false;
}
- public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
- if (state == null) {
- return (String[])ArrayUtils.emptyArray(String.class);
- }
- return state.getKeyboardLayouts();
- }
-
- public boolean addKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
- InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
- if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
- setDirty();
- return true;
- }
- return false;
- }
-
- public boolean removeKeyboardLayout(String inputDeviceDescriptor,
- String keyboardLayoutDescriptor) {
- InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
- if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
- setDirty();
- return true;
- }
- return false;
- }
-
- public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
- InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
- if (state != null && state.switchKeyboardLayout(direction)) {
- setDirty();
- return true;
- }
- return false;
- }
-
public boolean setKeyboardBacklightBrightness(String inputDeviceDescriptor, int lightId,
int brightness) {
InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
@@ -417,9 +362,6 @@
"x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
- @Nullable
- private String mCurrentKeyboardLayout;
- private final ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
private final SparseIntArray mKeyboardBacklightBrightnessMap = new SparseIntArray();
private final Map<String, String> mKeyboardLayoutMap = new ArrayMap<>();
@@ -465,49 +407,6 @@
return true;
}
- @Nullable
- public String getCurrentKeyboardLayout() {
- return mCurrentKeyboardLayout;
- }
-
- public boolean setCurrentKeyboardLayout(String keyboardLayout) {
- if (Objects.equals(mCurrentKeyboardLayout, keyboardLayout)) {
- return false;
- }
- addKeyboardLayout(keyboardLayout);
- mCurrentKeyboardLayout = keyboardLayout;
- return true;
- }
-
- public String[] getKeyboardLayouts() {
- if (mKeyboardLayouts.isEmpty()) {
- return (String[])ArrayUtils.emptyArray(String.class);
- }
- return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
- }
-
- public boolean addKeyboardLayout(String keyboardLayout) {
- int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
- if (index >= 0) {
- return false;
- }
- mKeyboardLayouts.add(-index - 1, keyboardLayout);
- if (mCurrentKeyboardLayout == null) {
- mCurrentKeyboardLayout = keyboardLayout;
- }
- return true;
- }
-
- public boolean removeKeyboardLayout(String keyboardLayout) {
- int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
- if (index < 0) {
- return false;
- }
- mKeyboardLayouts.remove(index);
- updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
- return true;
- }
-
public boolean setKeyboardBacklightBrightness(int lightId, int brightness) {
if (mKeyboardBacklightBrightnessMap.get(lightId, INVALID_VALUE) == brightness) {
return false;
@@ -521,48 +420,8 @@
return brightness == INVALID_VALUE ? OptionalInt.empty() : OptionalInt.of(brightness);
}
- private void updateCurrentKeyboardLayoutIfRemoved(
- String removedKeyboardLayout, int removedIndex) {
- if (Objects.equals(mCurrentKeyboardLayout, removedKeyboardLayout)) {
- if (!mKeyboardLayouts.isEmpty()) {
- int index = removedIndex;
- if (index == mKeyboardLayouts.size()) {
- index = 0;
- }
- mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
- } else {
- mCurrentKeyboardLayout = null;
- }
- }
- }
-
- public boolean switchKeyboardLayout(int direction) {
- final int size = mKeyboardLayouts.size();
- if (size < 2) {
- return false;
- }
- int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
- assert index >= 0;
- if (direction > 0) {
- index = (index + 1) % size;
- } else {
- index = (index + size - 1) % size;
- }
- mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
- return true;
- }
-
public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
boolean changed = false;
- for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
- String keyboardLayout = mKeyboardLayouts.get(i);
- if (!availableKeyboardLayouts.contains(keyboardLayout)) {
- Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
- mKeyboardLayouts.remove(i);
- updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
- changed = true;
- }
- }
List<String> removedEntries = new ArrayList<>();
for (String key : mKeyboardLayoutMap.keySet()) {
if (!availableKeyboardLayouts.contains(mKeyboardLayoutMap.get(key))) {
@@ -582,27 +441,7 @@
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals("keyboard-layout")) {
- String descriptor = parser.getAttributeValue(null, "descriptor");
- if (descriptor == null) {
- throw new XmlPullParserException(
- "Missing descriptor attribute on keyboard-layout.");
- }
- String current = parser.getAttributeValue(null, "current");
- if (mKeyboardLayouts.contains(descriptor)) {
- throw new XmlPullParserException(
- "Found duplicate keyboard layout.");
- }
-
- mKeyboardLayouts.add(descriptor);
- if (current != null && current.equals("true")) {
- if (mCurrentKeyboardLayout != null) {
- throw new XmlPullParserException(
- "Found multiple current keyboard layouts.");
- }
- mCurrentKeyboardLayout = descriptor;
- }
- } else if (parser.getName().equals("keyed-keyboard-layout")) {
+ if (parser.getName().equals("keyed-keyboard-layout")) {
String key = parser.getAttributeValue(null, "key");
if (key == null) {
throw new XmlPullParserException(
@@ -676,27 +515,9 @@
}
}
}
-
- // Maintain invariant that layouts are sorted.
- Collections.sort(mKeyboardLayouts);
-
- // Maintain invariant that there is always a current keyboard layout unless
- // there are none installed.
- if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
- mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
- }
}
public void saveToXml(TypedXmlSerializer serializer) throws IOException {
- for (String layout : mKeyboardLayouts) {
- serializer.startTag(null, "keyboard-layout");
- serializer.attribute(null, "descriptor", layout);
- if (layout.equals(mCurrentKeyboardLayout)) {
- serializer.attributeBoolean(null, "current", true);
- }
- serializer.endTag(null, "keyboard-layout");
- }
-
for (String key : mKeyboardLayoutMap.keySet()) {
serializer.startTag(null, "keyed-keyboard-layout");
serializer.attribute(null, "key", key);
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 31ce630..ffc2319 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -196,7 +196,10 @@
return true;
}
- private void sendResultReceiverFailure(ResultReceiver resultReceiver) {
+ private void sendResultReceiverFailure(@Nullable ResultReceiver resultReceiver) {
+ if (resultReceiver == null) {
+ return;
+ }
resultReceiver.send(
mIsInputShown.getAsBoolean()
? InputMethodManager.RESULT_UNCHANGED_SHOWN
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index c80f988..5931744 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -107,7 +107,7 @@
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.security.AndroidKeyStoreMaintenance;
-import android.security.Authorization;
+import android.security.KeyStoreAuthorization;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore.recovery.KeyChainProtectionParams;
@@ -293,6 +293,7 @@
private final SyntheticPasswordManager mSpManager;
private final KeyStore mKeyStore;
+ private final KeyStoreAuthorization mKeyStoreAuthorization;
private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache;
@@ -627,6 +628,10 @@
}
}
+ public KeyStoreAuthorization getKeyStoreAuthorization() {
+ return KeyStoreAuthorization.getInstance();
+ }
+
public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(KeyStore ks) {
return new UnifiedProfilePasswordCache(ks);
}
@@ -650,6 +655,7 @@
mInjector = injector;
mContext = injector.getContext();
mKeyStore = injector.getKeyStore();
+ mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
mHandler = injector.getHandler(injector.getServiceThread());
mStrongAuth = injector.getStrongAuth();
@@ -1460,7 +1466,7 @@
}
private void unlockKeystore(int userId, SyntheticPassword sp) {
- Authorization.onDeviceUnlocked(userId, sp.deriveKeyStorePassword());
+ mKeyStoreAuthorization.onDeviceUnlocked(userId, sp.deriveKeyStorePassword());
}
@VisibleForTesting /** Note: this method is overridden in unit tests */
diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
index 86ac96b..f27ade4 100644
--- a/services/core/java/com/android/server/media/AudioManagerRouteController.java
+++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java
@@ -136,7 +136,7 @@
mBluetoothRouteController =
new BluetoothDeviceRoutesManager(
- mContext, btAdapter, this::rebuildAvailableRoutesAndNotify);
+ mContext, mHandler, btAdapter, this::rebuildAvailableRoutesAndNotify);
// Just build routes but don't notify. The caller may not expect the listener to be invoked
// before this constructor has finished executing.
rebuildAvailableRoutes();
diff --git a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
index b881ef6..8b65ea3 100644
--- a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
+++ b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java
@@ -31,6 +31,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaRoute2Info;
+import android.os.Handler;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -77,26 +78,35 @@
@NonNull
private final Context mContext;
- @NonNull
- private final BluetoothAdapter mBluetoothAdapter;
+ @NonNull private final Handler mHandler;
+ @NonNull private final BluetoothAdapter mBluetoothAdapter;
@NonNull
private final BluetoothRouteController.BluetoothRoutesUpdatedListener mListener;
@NonNull
private final BluetoothProfileMonitor mBluetoothProfileMonitor;
- BluetoothDeviceRoutesManager(@NonNull Context context,
+ BluetoothDeviceRoutesManager(
+ @NonNull Context context,
+ @NonNull Handler handler,
@NonNull BluetoothAdapter bluetoothAdapter,
@NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
- this(context, bluetoothAdapter,
- new BluetoothProfileMonitor(context, bluetoothAdapter), listener);
+ this(
+ context,
+ handler,
+ bluetoothAdapter,
+ new BluetoothProfileMonitor(context, bluetoothAdapter),
+ listener);
}
@VisibleForTesting
- BluetoothDeviceRoutesManager(@NonNull Context context,
+ BluetoothDeviceRoutesManager(
+ @NonNull Context context,
+ @NonNull Handler handler,
@NonNull BluetoothAdapter bluetoothAdapter,
@NonNull BluetoothProfileMonitor bluetoothProfileMonitor,
@NonNull BluetoothRouteController.BluetoothRoutesUpdatedListener listener) {
mContext = Objects.requireNonNull(context);
+ mHandler = handler;
mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
mBluetoothProfileMonitor = Objects.requireNonNull(bluetoothProfileMonitor);
mListener = Objects.requireNonNull(listener);
@@ -298,6 +308,26 @@
};
}
+ private void handleBluetoothAdapterStateChange(int state) {
+ if (state == BluetoothAdapter.STATE_OFF || state == BluetoothAdapter.STATE_TURNING_OFF) {
+ synchronized (BluetoothDeviceRoutesManager.this) {
+ mBluetoothRoutes.clear();
+ }
+ notifyBluetoothRoutesUpdated();
+ } else if (state == BluetoothAdapter.STATE_ON) {
+ updateBluetoothRoutes();
+
+ boolean shouldCallListener;
+ synchronized (BluetoothDeviceRoutesManager.this) {
+ shouldCallListener = !mBluetoothRoutes.isEmpty();
+ }
+
+ if (shouldCallListener) {
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+
private static class BluetoothRouteInfo {
private BluetoothDevice mBtDevice;
private MediaRoute2Info mRoute;
@@ -308,23 +338,10 @@
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
- if (state == BluetoothAdapter.STATE_OFF
- || state == BluetoothAdapter.STATE_TURNING_OFF) {
- synchronized (BluetoothDeviceRoutesManager.this) {
- mBluetoothRoutes.clear();
- }
- notifyBluetoothRoutesUpdated();
- } else if (state == BluetoothAdapter.STATE_ON) {
- updateBluetoothRoutes();
-
- boolean shouldCallListener;
- synchronized (BluetoothDeviceRoutesManager.this) {
- shouldCallListener = !mBluetoothRoutes.isEmpty();
- }
-
- if (shouldCallListener) {
- notifyBluetoothRoutesUpdated();
- }
+ if (Flags.enableMr2ServiceNonMainBgThread()) {
+ mHandler.post(() -> handleBluetoothAdapterStateChange(state));
+ } else {
+ handleBluetoothAdapterStateChange(state);
}
}
}
@@ -337,8 +354,16 @@
case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
case BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED:
case BluetoothDevice.ACTION_ALIAS_CHANGED:
- updateBluetoothRoutes();
- notifyBluetoothRoutesUpdated();
+ if (Flags.enableMr2ServiceNonMainBgThread()) {
+ mHandler.post(
+ () -> {
+ updateBluetoothRoutes();
+ notifyBluetoothRoutesUpdated();
+ });
+ } else {
+ updateBluetoothRoutes();
+ notifyBluetoothRoutesUpdated();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaKeyDispatcher.java b/services/core/java/com/android/server/media/MediaKeyDispatcher.java
index 66cafab..e4f2ec3 100644
--- a/services/core/java/com/android/server/media/MediaKeyDispatcher.java
+++ b/services/core/java/com/android/server/media/MediaKeyDispatcher.java
@@ -44,7 +44,6 @@
* Note: When instantiating this class, {@link MediaSessionService} will only use the constructor
* without any parameters.
*/
-// TODO: Move this class to apex/media/
public abstract class MediaKeyDispatcher {
@IntDef(flag = true, value = {
KEY_EVENT_SINGLE_TAP,
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index aa71e05..e50189b 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -189,12 +189,10 @@
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
- if (!Flags.disableScreenOffBroadcastReceiver()) {
- IntentFilter screenOnOffIntentFilter = new IntentFilter();
- screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
- screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
- mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
- }
+ IntentFilter screenOnOffIntentFilter = new IntentFilter();
+ screenOnOffIntentFilter.addAction(ACTION_SCREEN_ON);
+ screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
+ mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
// Passing null package name to listen to all events.
mAppOpsManager.startWatchingMode(
@@ -3435,9 +3433,7 @@
@NonNull
private static List<RouterRecord> getIndividuallyActiveRouters(
MediaRouter2ServiceImpl service, List<RouterRecord> allRouterRecords) {
- if (!Flags.disableScreenOffBroadcastReceiver()
- && !service.mPowerManager.isInteractive()
- && !Flags.enableScreenOffScanning()) {
+ if (!service.mPowerManager.isInteractive() && !Flags.enableScreenOffScanning()) {
return Collections.emptyList();
}
@@ -3453,9 +3449,7 @@
private static boolean areManagersScanning(
MediaRouter2ServiceImpl service, List<ManagerRecord> managerRecords) {
- if (!Flags.disableScreenOffBroadcastReceiver()
- && !service.mPowerManager.isInteractive()
- && !Flags.enableScreenOffScanning()) {
+ if (!service.mPowerManager.isInteractive() && !Flags.enableScreenOffScanning()) {
return false;
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 4bdca29..1a129cb 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -53,6 +53,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -70,6 +71,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
+import com.android.media.flags.Flags;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
import com.android.server.pm.UserManagerInternal;
@@ -94,6 +96,7 @@
implements Watchdog.Monitor {
private static final String TAG = "MediaRouterService";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final String WORKER_THREAD_NAME = "MediaRouterServiceThread";
/**
* Timeout in milliseconds for a selected route to transition from a disconnected state to a
@@ -126,7 +129,7 @@
private final IAudioService mAudioService;
private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
- private final Handler mHandler = new Handler();
+ private final Handler mHandler;
private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
@@ -142,7 +145,14 @@
@RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
public MediaRouterService(Context context) {
- mLooper = Looper.getMainLooper();
+ if (Flags.enableMr2ServiceNonMainBgThread()) {
+ HandlerThread handlerThread = new HandlerThread(WORKER_THREAD_NAME);
+ handlerThread.start();
+ mLooper = handlerThread.getLooper();
+ } else {
+ mLooper = Looper.myLooper();
+ }
+ mHandler = new Handler(mLooper);
mService2 = new MediaRouter2ServiceImpl(context, mLooper);
mContext = context;
Watchdog.getInstance().addMonitor(this);
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index eb4e6e4..62a9471 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -849,7 +849,7 @@
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -876,7 +876,7 @@
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -911,7 +911,7 @@
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -938,7 +938,7 @@
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -965,7 +965,7 @@
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -992,7 +992,7 @@
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -1017,7 +1017,7 @@
}
}
if (deadCallbackHolders != null) {
- mControllerCallbackHolders.removeAll(deadCallbackHolders);
+ removeControllerHoldersSafely(deadCallbackHolders);
}
}
@@ -1042,7 +1042,7 @@
}
}
// After notifying clear all listeners
- mControllerCallbackHolders.clear();
+ removeControllerHoldersSafely(null);
}
private PlaybackState getStateWithUpdatedPosition() {
@@ -1090,6 +1090,17 @@
return -1;
}
+ private void removeControllerHoldersSafely(
+ Collection<ISessionControllerCallbackHolder> holders) {
+ synchronized (mLock) {
+ if (holders == null) {
+ mControllerCallbackHolders.clear();
+ } else {
+ mControllerCallbackHolders.removeAll(holders);
+ }
+ }
+ }
+
private PlaybackInfo getVolumeAttributes() {
int volumeType;
AudioAttributes attributes;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 802acba..8df38a8 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -653,9 +653,11 @@
return;
}
- // TODO: b/310145678 - Post this to mHandler once mHandler does not run on the main
- // thread.
- updateVolume();
+ if (Flags.enableMr2ServiceNonMainBgThread()) {
+ mHandler.post(SystemMediaRoute2Provider.this::updateVolume);
+ } else {
+ updateVolume();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/net/Android.bp b/services/core/java/com/android/server/net/Android.bp
index 71d8e6b..3ac2d23 100644
--- a/services/core/java/com/android/server/net/Android.bp
+++ b/services/core/java/com/android/server/net/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "net_flags",
package: "com.android.server.net",
+ container: "system",
srcs: ["*.aconfig"],
}
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
index 419665a..d9491de 100644
--- a/services/core/java/com/android/server/net/flags.aconfig
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.net"
+container: "system"
flag {
name: "network_blocked_for_top_sleeping_and_above"
diff --git a/services/core/java/com/android/server/notification/Android.bp b/services/core/java/com/android/server/notification/Android.bp
index 9be4358..d757470 100644
--- a/services/core/java/com/android/server/notification/Android.bp
+++ b/services/core/java/com/android/server/notification/Android.bp
@@ -10,6 +10,7 @@
aconfig_declarations {
name: "notification_flags",
package: "com.android.server.notification",
+ container: "system",
srcs: [
"flags.aconfig",
],
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index e1e2b3e..3949dfe 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1617,7 +1617,8 @@
intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mConfig.clientLabel);
final ActivityOptions activityOptions = ActivityOptions.makeBasic();
- activityOptions.setIgnorePendingIntentCreatorForegroundState(true);
+ activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
final PendingIntent pendingIntent = PendingIntent.getActivity(
mContext, 0, new Intent(mConfig.settingsAction), PendingIntent.FLAG_IMMUTABLE,
activityOptions.toBundle());
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 24f6c70..956e10c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1320,7 +1320,7 @@
// Notifications that have been interacted with should no longer be lifetime
// extended.
if (lifetimeExtensionRefactor()) {
- // Enqueue a cancellation; this cancellation should only work if
+ // This cancellation should only work if
// the notification still has FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
// We wait for 200 milliseconds before posting the cancel, to allow the app
// time to update the notification in response instead.
@@ -1337,7 +1337,7 @@
FLAG_NO_DISMISS /*=mustNotHaveFlags*/,
false /*=sendDelete*/,
r.getUserId(),
- REASON_APP_CANCEL,
+ REASON_CLICK,
-1 /*=rank*/,
-1 /*=count*/,
null /*=listener*/,
@@ -1855,14 +1855,6 @@
FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
| FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY,
true, record.getUserId(), REASON_TIMEOUT, null);
- // If cancellation will be prevented due to lifetime extension, we send an
- // update to system UI.
- final int packageImportance = getPackageImportanceWithIdentity(
- record.getSbn().getPackageName());
- synchronized (mNotificationLock) {
- maybeNotifySystemUiListenerLifetimeExtendedLocked(record,
- record.getSbn().getPackageName(), packageImportance);
- }
} else {
cancelNotification(record.getSbn().getUid(),
record.getSbn().getInitialPid(),
@@ -3673,15 +3665,6 @@
if (lifetimeExtensionRefactor()) {
// Also don't allow client apps to cancel lifetime extended notifs.
mustNotHaveFlags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
- // If cancellation will be prevented due to lifetime extension, we send an update to
- // system UI.
- NotificationRecord record = null;
- final int packageImportance = getPackageImportanceWithIdentity(pkg);
- synchronized (mNotificationLock) {
- record = findNotificationLocked(pkg, tag, id, userId);
- maybeNotifySystemUiListenerLifetimeExtendedLocked(record, pkg,
- packageImportance);
- }
}
cancelNotificationInternal(pkg, opPkg, Binder.getCallingUid(), Binder.getCallingPid(),
@@ -4878,7 +4861,7 @@
|| isNotificationRecent(r.getUpdateTimeMs());
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
r.getSbn().getPackageName(), r.getSbn().getTag(),
- r.getSbn().getId(), userId, reason, packageImportance);
+ r.getSbn().getId(), userId, reason);
}
} else {
for (NotificationRecord notificationRecord : mNotificationList) {
@@ -5018,14 +5001,10 @@
@GuardedBy("mNotificationLock")
private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
int callingUid, int callingPid, String pkg, String tag, int id, int userId,
- int reason, int packageImportance) {
+ int reason) {
int mustNotHaveFlags = FLAG_ONGOING_EVENT;
if (lifetimeExtensionRefactor()) {
mustNotHaveFlags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
- // If cancellation will be prevented due to lifetime extension, we send an update
- // to system UI.
- NotificationRecord record = findNotificationLocked(pkg, tag, id, userId);
- maybeNotifySystemUiListenerLifetimeExtendedLocked(record, pkg, packageImportance);
}
cancelNotification(callingUid, callingPid, pkg, tag, id, 0 /* mustHaveFlags */,
mustNotHaveFlags,
@@ -5168,13 +5147,7 @@
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final long identity = Binder.clearCallingIdentity();
- final int packageImportance;
try {
- if (lifetimeExtensionRefactor()) {
- packageImportance = getPackageImportanceWithIdentity(pkg);
- } else {
- packageImportance = IMPORTANCE_NONE;
- }
synchronized (mNotificationLock) {
final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
int cancelReason = REASON_LISTENER_CANCEL;
@@ -5187,7 +5160,7 @@
+ " use cancelNotification(key) instead.");
} else {
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
- pkg, tag, id, info.userid, cancelReason, packageImportance);
+ pkg, tag, id, info.userid, cancelReason);
}
}
} finally {
@@ -8178,19 +8151,30 @@
EventLogTags.writeNotificationCancel(mCallingUid, mCallingPid, mPkg, mId, mTag,
mUserId, mMustHaveFlags, mMustNotHaveFlags, mReason, listenerName);
}
-
+ int packageImportance = IMPORTANCE_NONE;
+ if (lifetimeExtensionRefactor()) {
+ packageImportance = getPackageImportanceWithIdentity(mPkg);
+ }
synchronized (mNotificationLock) {
// Look for the notification, searching both the posted and enqueued lists.
NotificationRecord r = findNotificationLocked(mPkg, mTag, mId, mUserId);
+
if (r != null) {
// The notification was found, check if it should be removed.
-
// Ideally we'd do this in the caller of this method. However, that would
// require the caller to also find the notification.
if (mReason == REASON_CLICK) {
mUsageStats.registerClickedByUser(r);
}
+ // If cancellation will be prevented due to lifetime extension, we need to
+ // send an update to system UI. This must be checked before flags are checked.
+ // We do not want to send this update.
+ if (lifetimeExtensionRefactor() && mReason != REASON_CLICK) {
+ maybeNotifySystemUiListenerLifetimeExtendedLocked(r, mPkg,
+ packageImportance);
+ }
+
if ((mReason == REASON_LISTENER_CANCEL
&& r.getNotification().isBubbleNotification())
|| (mReason == REASON_CLICK && r.canBubble()
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index d4641c4..077ed5a 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.notification"
+container: "system"
flag {
name: "expire_bitmaps"
diff --git a/services/core/java/com/android/server/os/Android.bp b/services/core/java/com/android/server/os/Android.bp
index 565dc3b..c588670 100644
--- a/services/core/java/com/android/server/os/Android.bp
+++ b/services/core/java/com/android/server/os/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "core_os_flags",
package: "com.android.server.os",
+ container: "system",
srcs: [
"*.aconfig",
],
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 4eb8b2b..c8fd7e4 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -184,6 +184,16 @@
throwInvalidBugreportFileForCallerException(
bugreportFile, callingInfo.second);
}
+
+ boolean keepBugreportOnRetrieval = false;
+ if (onboardingBugreportV2Enabled()) {
+ keepBugreportOnRetrieval = mBugreportFilesToPersist.contains(
+ bugreportFile);
+ }
+
+ if (!keepBugreportOnRetrieval) {
+ bugreportFilesForUid.remove(bugreportFile);
+ }
} else {
ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
if (bugreportFilesForCaller != null
diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig
index cbc0d54..ae33df8 100644
--- a/services/core/java/com/android/server/os/core_os_flags.aconfig
+++ b/services/core/java/com/android/server/os/core_os_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.os"
+container: "system"
flag {
name: "proto_tombstone"
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 017cf55..306f77d 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.hardware.usb.UsbManager;
+import android.nfc.NfcAdapter;
import android.provider.AlarmClock;
import android.provider.MediaStore;
@@ -361,6 +362,7 @@
.addCategory(Intent.CATEGORY_DEFAULT)
.build();
+
public static List<DefaultCrossProfileIntentFilter> getDefaultManagedProfileFilters() {
List<DefaultCrossProfileIntentFilter> filters =
new ArrayList<DefaultCrossProfileIntentFilter>();
@@ -637,6 +639,33 @@
.addDataType("video/*")
.build();
+ /** NFC TAG intent is always resolved by the primary user. */
+ static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_NFC_TAG_DISCOVERED =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */0,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(NfcAdapter.ACTION_TAG_DISCOVERED)
+ .build();
+
+ /** NFC TAG intent is always resolved by the primary user. */
+ static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_NFC_TECH_DISCOVERED =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */0,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(NfcAdapter.ACTION_TECH_DISCOVERED)
+ .build();
+
+ /** NFC TAG intent is always resolved by the primary user. */
+ static final DefaultCrossProfileIntentFilter PARENT_TO_CLONE_NFC_NDEF_DISCOVERED =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
+ /* flags= */0,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(NfcAdapter.ACTION_NDEF_DISCOVERED)
+ .build();
+
public static List<DefaultCrossProfileIntentFilter> getDefaultCloneProfileFilters() {
return Arrays.asList(
PARENT_TO_CLONE_SEND_ACTION,
@@ -652,7 +681,10 @@
CLONE_TO_PARENT_SMS_MMS,
CLONE_TO_PARENT_PHOTOPICKER_SELECTION,
CLONE_TO_PARENT_ACTION_PICK_IMAGES,
- CLONE_TO_PARENT_ACTION_PICK_IMAGES_WITH_DATA_TYPES
+ CLONE_TO_PARENT_ACTION_PICK_IMAGES_WITH_DATA_TYPES,
+ PARENT_TO_CLONE_NFC_TAG_DISCOVERED,
+ PARENT_TO_CLONE_NFC_TECH_DISCOVERED,
+ PARENT_TO_CLONE_NFC_NDEF_DISCOVERED
);
}
diff --git a/services/core/java/com/android/server/pm/NoFilteringResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java
index b87256d..712413c 100644
--- a/services/core/java/com/android/server/pm/NoFilteringResolver.java
+++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java
@@ -60,9 +60,12 @@
public static boolean isIntentRedirectionAllowed(Context context,
AppCloningDeviceConfigHelper appCloningDeviceConfigHelper, boolean resolveForStart,
long flags) {
+ boolean canMatchCloneProfile = (flags & PackageManager.MATCH_CLONE_PROFILE) != 0
+ || (flags & PackageManager.MATCH_CLONE_PROFILE_LONG) != 0;
return isAppCloningBuildingBlocksEnabled(context, appCloningDeviceConfigHelper)
- && (resolveForStart || (((flags & PackageManager.MATCH_CLONE_PROFILE) != 0)
- && hasPermission(context, Manifest.permission.QUERY_CLONED_APPS)));
+ && (resolveForStart
+ || (canMatchCloneProfile
+ && hasPermission(context, Manifest.permission.QUERY_CLONED_APPS)));
}
public NoFilteringResolver(ComponentResolverApi componentResolver,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 29320ae..a5cd821 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -838,7 +838,8 @@
if ((params.installFlags & PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK) != 0
&& !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)
- && !Build.IS_DEBUGGABLE) {
+ && !Build.IS_DEBUGGABLE
+ && !PackageManagerServiceUtils.isAdoptedShell(callingUid, mContext)) {
// If the bypass flag is set, but not running as system root or shell then remove
// the flag
params.installFlags &= ~PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3eeeae7..80a5f3a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1085,11 +1085,17 @@
final boolean isUpdateOwnershipEnforcementEnabled =
mPm.isUpdateOwnershipEnforcementAvailable()
&& existingUpdateOwnerPackageName != null;
+ // For an installation that un-archives an app, if the installer doesn't have the
+ // INSTALL_PACKAGES permission, the user should have already been prompted to confirm the
+ // un-archive request. There's no need for another confirmation during the installation.
+ final boolean isInstallUnarchive =
+ (params.installFlags & PackageManager.INSTALL_UNARCHIVE) != 0;
// Device owners and affiliated profile owners are allowed to silently install packages, so
// the permission check is waived if the installer is the device owner.
final boolean noUserActionNecessary = isInstallerRoot || isInstallerSystem
- || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall;
+ || isInstallerDeviceOwnerOrAffiliatedProfileOwner() || isEmergencyInstall
+ || isInstallUnarchive;
if (noUserActionNecessary) {
return userActionNotTypicallyNeededResponse;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 034e467..1793794 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3387,6 +3387,18 @@
}
sessionParams.setEnableRollback(true, rollbackStrategy);
break;
+ case "--rollback-impact-level":
+ if (!Flags.recoverabilityDetection()) {
+ throw new IllegalArgumentException("Unknown option " + opt);
+ }
+ int rollbackImpactLevel = Integer.parseInt(peekNextArg());
+ if (rollbackImpactLevel < PackageManager.ROLLBACK_USER_IMPACT_LOW
+ || rollbackImpactLevel
+ > PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL) {
+ throw new IllegalArgumentException(
+ rollbackImpactLevel + " is not a valid rollback impact level.");
+ }
+ sessionParams.setRollbackImpactLevel(rollbackImpactLevel);
case "--staged-ready-timeout":
params.stagedReadyTimeoutMs = Long.parseLong(getNextArgRequired());
break;
@@ -4571,6 +4583,11 @@
pw.println(" --full: cause the app to be installed as a non-ephemeral full app");
pw.println(" --enable-rollback: enable rollbacks for the upgrade.");
pw.println(" 0=restore (default), 1=wipe, 2=retain");
+ if (Flags.recoverabilityDetection()) {
+ pw.println(
+ " --rollback-impact-level: set device impact required for rollback.");
+ pw.println(" 0=low (default), 1=high, 2=manual only");
+ }
pw.println(" --install-location: force the install location:");
pw.println(" 0=auto, 1=internal only, 2=prefer external");
pw.println(" --install-reason: indicates why the app is being installed:");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ff41245..e3261bd 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1882,11 +1882,10 @@
&& android.multiuser.Flags.enablePrivateSpaceFeatures()) {
// Allow delayed locking since some profile types want to be able to unlock again via
// biometrics.
- ActivityManager.getService()
- .stopUserWithDelayedLocking(userId, /* force= */ true, null);
+ ActivityManager.getService().stopUserWithDelayedLocking(userId, null);
return;
}
- ActivityManager.getService().stopUser(userId, /* force= */ true, null);
+ ActivityManager.getService().stopUserWithCallback(userId, null);
}
private void logQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode,
@@ -6132,7 +6131,7 @@
if (DBG) Slog.i(LOG_TAG, "Stopping user " + userId);
int res;
try {
- res = ActivityManager.getService().stopUser(userId, /* force= */ true,
+ res = ActivityManager.getService().stopUserWithCallback(userId,
new IStopUserCallback.Stub() {
@Override
public void userStopped(int userIdParam) {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 324637c..4e02470 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -721,7 +721,8 @@
if (!am.isProfileForeground(UserHandle.of(userId))
&& userId != UserHandle.USER_SYSTEM) {
try {
- ActivityManager.getService().stopUser(userId, false, null);
+ ActivityManager.getService().stopUserExceptCertainProfiles(
+ userId, false, null);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
diff --git a/services/core/java/com/android/server/policy/Android.bp b/services/core/java/com/android/server/policy/Android.bp
index fa55bf0..325b6cb 100644
--- a/services/core/java/com/android/server/policy/Android.bp
+++ b/services/core/java/com/android/server/policy/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "policy_flags",
package: "com.android.server.policy",
+ container: "system",
srcs: ["*.aconfig"],
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7db83d7..b4919a4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -174,7 +174,6 @@
import android.service.vr.IPersistentVrStateCallbacks;
import android.speech.RecognizerIntent;
import android.telecom.TelecomManager;
-import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.MutableBoolean;
@@ -4121,14 +4120,10 @@
private void handleSwitchKeyboardLayout(@NonNull KeyEvent event, int direction,
IBinder focusedToken) {
- if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
- IBinder targetWindowToken =
- mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
- InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
- event.getDisplayId(), targetWindowToken);
- } else {
- mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
- }
+ IBinder targetWindowToken =
+ mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
+ InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
+ event.getDisplayId(), targetWindowToken);
}
private boolean interceptFallback(IBinder focusedToken, KeyEvent fallbackEvent,
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 2623025..9ca4e27 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -251,12 +251,6 @@
*/
public int getCameraLensCoverState();
- /**
- * Switch the keyboard layout for the given device.
- * Direction should be +1 or -1 to go to the next or previous keyboard layout.
- */
- public void switchKeyboardLayout(int deviceId, int direction);
-
public void shutdown(boolean confirm);
public void reboot(boolean confirm);
public void rebootSafeMode(boolean confirm);
diff --git a/services/core/java/com/android/server/policy/window_policy_flags.aconfig b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
index 2154a26..7914b94 100644
--- a/services/core/java/com/android/server/policy/window_policy_flags.aconfig
+++ b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.policy"
+container: "system"
flag {
name: "support_input_wakeup_delegate"
diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp
index 863ff76..5d4065d 100644
--- a/services/core/java/com/android/server/power/Android.bp
+++ b/services/core/java/com/android/server/power/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "backstage_power_flags",
package: "com.android.server.power.optimization",
+ container: "system",
srcs: [
"stats/*.aconfig",
],
diff --git a/services/core/java/com/android/server/power/batterysaver/OWNERS b/services/core/java/com/android/server/power/batterysaver/OWNERS
index cf23bea..dc2d0b3 100644
--- a/services/core/java/com/android/server/power/batterysaver/OWNERS
+++ b/services/core/java/com/android/server/power/batterysaver/OWNERS
@@ -1,3 +1,2 @@
-kwekua@google.com
omakoto@google.com
-yamasani@google.com
\ No newline at end of file
+yamasani@google.com
diff --git a/services/core/java/com/android/server/power/hint/Android.bp b/services/core/java/com/android/server/power/hint/Android.bp
index 8a98de6..d7dd902 100644
--- a/services/core/java/com/android/server/power/hint/Android.bp
+++ b/services/core/java/com/android/server/power/hint/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "power_hint_flags",
package: "com.android.server.power.hint",
+ container: "system",
srcs: [
"flags.aconfig",
],
diff --git a/services/core/java/com/android/server/power/hint/flags.aconfig b/services/core/java/com/android/server/power/hint/flags.aconfig
index f4afcb1..0997744 100644
--- a/services/core/java/com/android/server/power/hint/flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.power.hint"
+container: "system"
flag {
name: "powerhint_thread_cleanup"
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index c42ccea..54ae84f4 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.power.optimization"
+container: "system"
flag {
name: "power_monitor_api"
diff --git a/services/core/java/com/android/server/rollback/README.md b/services/core/java/com/android/server/rollback/README.md
index f6bcbd0..a38315c 100644
--- a/services/core/java/com/android/server/rollback/README.md
+++ b/services/core/java/com/android/server/rollback/README.md
@@ -203,6 +203,15 @@
$ adb install --enable-rollback 1 FooV2.apk
```
+### Setting Rollback Impact Level
+
+The `adb install` command accepts the `--rollback-impact-level [0/1/2]` flag to control
+when a rollback can be performed by `PackageWatchdog`.
+
+The default rollback impact level is `ROLLBACK_USER_IMPACT_LOW` (0). To use a
+different impact level, use `ROLLBACK_USER_IMPACT_HIGH` (1) or `ROLLBACK_USER_IMPACT_ONLY_MANUAL`
+(2).
+
### Triggering Rollback Manually
If rollback is available for an application, the pm command can be used to
@@ -225,6 +234,8 @@
469808841:
-state: committed
-timestamp: 2019-04-23T14:57:35.944Z
+ -rollbackLifetimeMillis: 0
+ -rollbackUserImpact: 1
-packages:
com.android.tests.rollback.testapp.B 2 -> 1 [0]
-causePackages:
@@ -232,6 +243,8 @@
649899517:
-state: committed
-timestamp: 2019-04-23T12:55:21.342Z
+ -rollbackLifetimeMillis: 0
+ -rollbackUserImpact: 0
-stagedSessionId: 343374391
-packages:
com.android.tests.rollback.testapex 2 -> 1 [0]
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index d1f91c8..8f39ffb 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -27,6 +27,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.Flags;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -960,6 +961,9 @@
ipw.println("-stateDescription: " + mStateDescription);
ipw.println("-timestamp: " + getTimestamp());
ipw.println("-rollbackLifetimeMillis: " + getRollbackLifetimeMillis());
+ if (Flags.recoverabilityDetection()) {
+ ipw.println("-rollbackImpactLevel: " + info.getRollbackImpactLevel());
+ }
ipw.println("-isStaged: " + isStaged());
ipw.println("-originalSessionId: " + getOriginalSessionId());
ipw.println("-packages:");
diff --git a/services/core/java/com/android/server/stats/Android.bp b/services/core/java/com/android/server/stats/Android.bp
index e597c3a..f7955e8 100644
--- a/services/core/java/com/android/server/stats/Android.bp
+++ b/services/core/java/com/android/server/stats/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "stats_flags",
package: "com.android.server.stats",
+ container: "system",
srcs: [
"stats_flags.aconfig",
],
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index 5101a69..101b98e 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.stats"
+container: "system"
flag {
name: "add_mobile_bytes_transfer_by_proc_state_puller"
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 2b05993..f62c76e 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -61,7 +61,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.security.Authorization;
+import android.security.KeyStoreAuthorization;
import android.service.trust.GrantTrustResult;
import android.service.trust.TrustAgentService;
import android.text.TextUtils;
@@ -155,6 +155,7 @@
/* package */ final TrustArchive mArchive = new TrustArchive();
private final Context mContext;
private final LockPatternUtils mLockPatternUtils;
+ private final KeyStoreAuthorization mKeyStoreAuthorization;
private final UserManager mUserManager;
private final ActivityManager mActivityManager;
private FingerprintManager mFingerprintManager;
@@ -252,25 +253,27 @@
* cases.
*/
protected static class Injector {
- private final LockPatternUtils mLockPatternUtils;
- private final Looper mLooper;
+ private final Context mContext;
- public Injector(LockPatternUtils lockPatternUtils, Looper looper) {
- mLockPatternUtils = lockPatternUtils;
- mLooper = looper;
+ public Injector(Context context) {
+ mContext = context;
}
LockPatternUtils getLockPatternUtils() {
- return mLockPatternUtils;
+ return new LockPatternUtils(mContext);
+ }
+
+ KeyStoreAuthorization getKeyStoreAuthorization() {
+ return KeyStoreAuthorization.getInstance();
}
Looper getLooper() {
- return mLooper;
+ return Looper.myLooper();
}
}
public TrustManagerService(Context context) {
- this(context, new Injector(new LockPatternUtils(context), Looper.myLooper()));
+ this(context, new Injector(context));
}
protected TrustManagerService(Context context, Injector injector) {
@@ -280,6 +283,7 @@
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
mLockPatternUtils = injector.getLockPatternUtils();
+ mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
mStrongAuthTracker = new StrongAuthTracker(context, injector.getLooper());
mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
}
@@ -915,16 +919,16 @@
int authUserId = mLockPatternUtils.isProfileWithUnifiedChallenge(userId)
? resolveProfileParent(userId) : userId;
- Authorization.onDeviceLocked(userId, getBiometricSids(authUserId),
+ mKeyStoreAuthorization.onDeviceLocked(userId, getBiometricSids(authUserId),
isWeakUnlockMethodEnabled(authUserId));
} else {
- Authorization.onDeviceLocked(userId, getBiometricSids(userId), false);
+ mKeyStoreAuthorization.onDeviceLocked(userId, getBiometricSids(userId), false);
}
} else {
// Notify Keystore that the device is now unlocked for the user. Note that for unlocks
// with LSKF, this is redundant with the call from LockSettingsService which provides
// the password. However, for unlocks with biometric or trust agent, this is required.
- Authorization.onDeviceUnlocked(userId, /* password= */ null);
+ mKeyStoreAuthorization.onDeviceUnlocked(userId, /* password= */ null);
}
}
diff --git a/services/core/java/com/android/server/utils/Android.bp b/services/core/java/com/android/server/utils/Android.bp
index 3a334be..ffb9aec 100644
--- a/services/core/java/com/android/server/utils/Android.bp
+++ b/services/core/java/com/android/server/utils/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "com.android.server.utils-aconfig",
package: "com.android.server.utils",
+ container: "system",
srcs: ["*.aconfig"],
}
diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig
index 163116b..6f37837 100644
--- a/services/core/java/com/android/server/utils/flags.aconfig
+++ b/services/core/java/com/android/server/utils/flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.utils"
+container: "system"
flag {
name: "anr_timer_service"
diff --git a/services/core/java/com/android/server/utils/quota/OWNERS b/services/core/java/com/android/server/utils/quota/OWNERS
index a2943f3..469acb2 100644
--- a/services/core/java/com/android/server/utils/quota/OWNERS
+++ b/services/core/java/com/android/server/utils/quota/OWNERS
@@ -1,4 +1,3 @@
dplotnikov@google.com
-kwekua@google.com
omakoto@google.com
yamasani@google.com
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 6fc9d9a..ad54efc 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -32,8 +32,9 @@
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
@@ -42,10 +43,11 @@
* The base class for all vibrations.
*/
abstract class Vibration {
- private static final SimpleDateFormat DEBUG_TIME_FORMAT =
- new SimpleDateFormat("HH:mm:ss.SSS");
- private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT =
- new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+ private static final DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+ "HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+ private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+ "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
+
// Used to generate globally unique vibration ids.
private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
@@ -240,10 +242,12 @@
@Override
public String toString() {
- return "createTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime))
- + ", startTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime))
- + ", endTime: "
- + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime)))
+ return "createTime: " + DEBUG_DATE_TIME_FORMATTER.format(
+ Instant.ofEpochMilli(mCreateTime))
+ + ", startTime: " + DEBUG_DATE_TIME_FORMATTER.format(
+ Instant.ofEpochMilli(mStartTime))
+ + ", endTime: " + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMATTER.format(
+ Instant.ofEpochMilli(mEndTime)))
+ ", durationMs: " + mDurationMs
+ ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+ ", playedEffect: " + mPlayedEffect
@@ -267,12 +271,14 @@
boolean isExternalVibration = mPlayedEffect == null;
String timingsStr = String.format(Locale.ROOT,
"%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
- DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)),
+ DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)),
isExternalVibration ? "external" : "effect",
mStatus.name().toLowerCase(Locale.ROOT),
mDurationMs,
- mStartTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mStartTime)),
- mEndTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mEndTime)));
+ mStartTime == 0 ? ""
+ : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mStartTime)),
+ mEndTime == 0 ? ""
+ : DEBUG_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime)));
String paramStr = String.format(Locale.ROOT,
" | scale: %8s (adaptive=%.2f) | flags: %4s | usage: %s",
VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale,
@@ -307,10 +313,12 @@
pw.increaseIndent();
pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
pw.println("durationMs = " + mDurationMs);
- pw.println("createTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)));
- pw.println("startTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime)));
- pw.println("endTime = "
- + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime))));
+ pw.println("createTime = " + DEBUG_DATE_TIME_FORMATTER.format(
+ Instant.ofEpochMilli(mCreateTime)));
+ pw.println("startTime = " + DEBUG_DATE_TIME_FORMATTER.format(
+ Instant.ofEpochMilli(mStartTime)));
+ pw.println("endTime = " + (mEndTime == 0 ? null
+ : DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mEndTime))));
pw.println("playedEffect = " + mPlayedEffect);
pw.println("originalEffect = " + mOriginalEffect);
pw.println("scale = " + VibrationScaler.scaleLevelToString(mScaleLevel));
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 10317c9..b33fa6f 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -51,8 +51,9 @@
import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
@@ -66,8 +67,8 @@
private static final int UNRECOGNIZED_VIBRATION_TYPE = -1;
private static final int NO_SCALE = -1;
- private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT =
- new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+ private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+ "MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
private final VibrationParamsRecords mVibrationParamsRecords;
private final VibratorControllerHolder mVibratorControllerHolder;
@@ -567,7 +568,7 @@
public void dump(IndentingPrintWriter pw) {
String line = String.format(Locale.ROOT,
"%s | %6s | scale: %5s | typesMask: %6s | usages: %s",
- DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)),
+ DEBUG_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(mCreateTime)),
mOperation.name().toLowerCase(Locale.ROOT),
(mScale == NO_SCALE) ? "" : String.format(Locale.ROOT, "%.2f", mScale),
Long.toBinaryString(mTypesMask), createVibrationUsagesString());
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 5175b74..6905802 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -16,6 +16,7 @@
package com.android.server.wallpaper;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
import static android.app.WallpaperManager.getOrientation;
import static android.app.WallpaperManager.getRotatedOrientation;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -86,7 +87,7 @@
public interface WallpaperCropUtils {
/**
- * Equivalent to {@link #getCrop(Point, Point, SparseArray, boolean)}
+ * Equivalent to {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}
*/
Rect getCrop(Point displaySize, Point bitmapSize,
SparseArray<Rect> suggestedCrops, boolean rtl);
@@ -120,16 +121,23 @@
public Rect getCrop(Point displaySize, Point bitmapSize,
SparseArray<Rect> suggestedCrops, boolean rtl) {
- // Case 1: if no crops are provided, center align the full image
+ int orientation = getOrientation(displaySize);
+
+ // Case 1: if no crops are provided, show the full image (from the left, or right if RTL).
if (suggestedCrops == null || suggestedCrops.size() == 0) {
- Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
- float scale = Math.min(
- ((float) bitmapSize.x) / displaySize.x,
- ((float) bitmapSize.y) / displaySize.y);
- crop.scale(scale);
- crop.offset((bitmapSize.x - crop.width()) / 2,
- (bitmapSize.y - crop.height()) / 2);
- return crop;
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+
+ // The first exception is if the device is a foldable and we're on the folded screen.
+ // In that case, show the center of what's on the unfolded screen.
+ int unfoldedOrientation = mWallpaperDisplayHelper.getUnfoldedOrientation(orientation);
+ if (unfoldedOrientation != ORIENTATION_UNKNOWN) {
+ // Let the system know that we're showing the full image on the unfolded screen
+ SparseArray<Rect> newSuggestedCrops = new SparseArray<>();
+ newSuggestedCrops.put(unfoldedOrientation, crop);
+ // This will fall into "Case 4" of this function and center the folded screen
+ return getCrop(displaySize, bitmapSize, newSuggestedCrops, rtl);
+ }
+ return getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
}
// If any suggested crop is invalid, fallback to case 1
@@ -142,8 +150,6 @@
}
}
- int orientation = getOrientation(displaySize);
-
// Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
Rect suggestedCrop = suggestedCrops.get(orientation);
if (suggestedCrop != null) {
@@ -168,11 +174,21 @@
suggestedCrop = suggestedCrops.get(unfoldedOrientation);
suggestedDisplaySize = defaultDisplaySizes.get(unfoldedOrientation);
if (suggestedCrop != null) {
- // only keep the visible part (without parallax)
+ // compute the visible part (without parallax) of the unfolded screen
Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
- return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
+ // compute the folded crop, at the center of the crop of the unfolded screen
+ Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
+ // if we removed some width, add it back to add a parallax effect
+ if (res.width() < adjustedCrop.width()) {
+ if (rtl) res.left = Math.min(res.left, adjustedCrop.left);
+ else res.right = Math.max(res.right, adjustedCrop.right);
+ // use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX
+ res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD);
+ }
+ return res;
}
+
// Case 5: if the device is a foldable, if we're looking for an unfolded orientation and
// have the suggested crop of the relative folded orientation, reuse it by adding content.
int foldedOrientation = mWallpaperDisplayHelper.getFoldedOrientation(orientation);
@@ -274,11 +290,8 @@
if (additionalWidthForParallax > MAX_PARALLAX) {
int widthToRemove = (int) Math.ceil(
(additionalWidthForParallax - MAX_PARALLAX) * screenRatio * crop.height());
- if (rtl) {
- adjustedCrop.left += widthToRemove;
- } else {
- adjustedCrop.right -= widthToRemove;
- }
+ adjustedCrop.left += widthToRemove / 2;
+ adjustedCrop.right -= widthToRemove / 2 + widthToRemove % 2;
}
} else {
// TODO (b/281648899) the third case is not always correct, fix that.
@@ -366,6 +379,24 @@
*/
SparseArray<Rect> getDefaultCrops(SparseArray<Rect> suggestedCrops, Point bitmapSize) {
+ // If the suggested crops is single-element map with (ORIENTATION_UNKNOWN, cropHint),
+ // Crop the bitmap using the cropHint and compute the crops for cropped bitmap.
+ Rect cropHint = suggestedCrops.get(ORIENTATION_UNKNOWN);
+ if (cropHint != null) {
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ if (suggestedCrops.size() != 1 || !bitmapRect.contains(cropHint)) {
+ Slog.w(TAG, "Couldn't get default crops from suggested crops " + suggestedCrops
+ + " for bitmap of size " + bitmapSize + "; ignoring suggested crops");
+ return getDefaultCrops(new SparseArray<>(), bitmapSize);
+ }
+ Point cropSize = new Point(cropHint.width(), cropHint.height());
+ SparseArray<Rect> relativeDefaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
+ for (int i = 0; i < relativeDefaultCrops.size(); i++) {
+ relativeDefaultCrops.valueAt(i).offset(cropHint.left, cropHint.top);
+ }
+ return relativeDefaultCrops;
+ }
+
SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
== View.LAYOUT_DIRECTION_RTL;
@@ -422,26 +453,74 @@
} else {
boolean needCrop = false;
boolean needScale;
- boolean multiCrop = multiCrop() && wallpaper.mSupportsMultiCrop;
Point bitmapSize = new Point(options.outWidth, options.outHeight);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ if (multiCrop()) {
+ // Check that the suggested crops per screen orientation are all within the bitmap.
+ for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
+ int orientation = wallpaper.mCropHints.keyAt(i);
+ Rect crop = wallpaper.mCropHints.valueAt(i);
+ if (crop.isEmpty() || !bitmapRect.contains(crop)) {
+ Slog.w(TAG, "Invalid crop " + crop + " for orientation " + orientation
+ + " and bitmap size " + bitmapSize + "; clearing suggested crops.");
+ wallpaper.mCropHints.clear();
+ wallpaper.cropHint.set(bitmapRect);
+ break;
+ }
+ }
+ }
final Rect cropHint;
- if (multiCrop) {
- SparseArray<Rect> defaultDisplayCrops =
- getDefaultCrops(wallpaper.mCropHints, bitmapSize);
- // adapt the entries in wallpaper.mCropHints for the actual display
+
+ // A wallpaper with cropHints = Map.of(ORIENTATION_UNKNOWN, rect) is treated like
+ // a wallpaper with cropHints = null and cropHint = rect.
+ Rect tempCropHint = wallpaper.mCropHints.get(ORIENTATION_UNKNOWN);
+ if (multiCrop() && tempCropHint != null) {
+ wallpaper.cropHint.set(tempCropHint);
+ wallpaper.mCropHints.clear();
+ }
+ if (multiCrop() && wallpaper.mCropHints.size() > 0) {
+ // Some suggested crops per screen orientation were provided,
+ // use them to compute the default crops for this device
+ SparseArray<Rect> defaultCrops = getDefaultCrops(wallpaper.mCropHints, bitmapSize);
+ // Adapt the provided crops to match the actual crops for the default display
SparseArray<Rect> updatedCropHints = new SparseArray<>();
for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
int orientation = wallpaper.mCropHints.keyAt(i);
- Rect defaultCrop = defaultDisplayCrops.get(orientation);
+ Rect defaultCrop = defaultCrops.get(orientation);
if (defaultCrop != null) {
updatedCropHints.put(orientation, defaultCrop);
}
}
wallpaper.mCropHints = updatedCropHints;
- cropHint = getTotalCrop(defaultDisplayCrops);
+
+ // Finally, compute the cropHint based on the default crops
+ cropHint = getTotalCrop(defaultCrops);
wallpaper.cropHint.set(cropHint);
+ if (DEBUG) {
+ Slog.d(TAG, "Generated default crops for wallpaper: " + defaultCrops
+ + " based on suggested crops: " + wallpaper.mCropHints);
+ }
+ } else if (multiCrop()) {
+ // No crops per screen orientation were provided, but an overall cropHint may be
+ // defined in wallpaper.cropHint. Compute the default crops for the sub-image
+ // defined by the cropHint, then recompute the cropHint based on the default crops.
+ // If the cropHint is empty or invalid, ignore it and use the full image.
+ if (wallpaper.cropHint.isEmpty()) wallpaper.cropHint.set(bitmapRect);
+ if (!bitmapRect.contains(wallpaper.cropHint)) {
+ Slog.w(TAG, "Ignoring wallpaper.cropHint = " + wallpaper.cropHint
+ + "; not within the bitmap of size " + bitmapSize);
+ wallpaper.cropHint.set(bitmapRect);
+ }
+ Point cropSize = new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height());
+ SparseArray<Rect> defaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
+ cropHint = getTotalCrop(defaultCrops);
+ cropHint.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
+ wallpaper.cropHint.set(cropHint);
+ if (DEBUG) {
+ Slog.d(TAG, "Generated default crops for wallpaper: " + defaultCrops);
+ }
} else {
cropHint = new Rect(wallpaper.cropHint);
}
@@ -455,11 +534,11 @@
}
// Empty crop means use the full image
- if (cropHint.isEmpty()) {
+ if (!multiCrop() && cropHint.isEmpty()) {
cropHint.left = cropHint.top = 0;
cropHint.right = options.outWidth;
cropHint.bottom = options.outHeight;
- } else {
+ } else if (!multiCrop()) {
// force the crop rect to lie within the measured bounds
int dx = cropHint.right > options.outWidth ? options.outWidth - cropHint.right : 0;
int dy = cropHint.bottom > options.outHeight
@@ -473,19 +552,19 @@
if (cropHint.top < 0) {
cropHint.top = 0;
}
-
- // Don't bother cropping if what we're left with is identity
- needCrop = (options.outHeight > cropHint.height()
- || options.outWidth > cropHint.width());
}
+ // Don't bother cropping if what we're left with is identity
+ needCrop = (options.outHeight > cropHint.height()
+ || options.outWidth > cropHint.width());
+
// scale if the crop height winds up not matching the recommended metrics
needScale = cropHint.height() > wpData.mHeight
|| cropHint.height() > GLHelper.getMaxTextureSize()
|| cropHint.width() > GLHelper.getMaxTextureSize();
//make sure screen aspect ratio is preserved if width is scaled under screen size
- if (needScale && !multiCrop) {
+ if (needScale && !multiCrop()) {
final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
final int newWidth = (int) (cropHint.width() * scaleByHeight);
if (newWidth < displayInfo.logicalWidth) {
@@ -543,7 +622,7 @@
final Rect estimateCrop = new Rect(cropHint);
estimateCrop.scale(1f / options.inSampleSize);
float hRatio = (float) wpData.mHeight / estimateCrop.height();
- if (multiCrop) {
+ if (multiCrop()) {
// make sure the crop height is at most the display largest dimension
hRatio = (float) mWallpaperDisplayHelper.getDefaultDisplayLargestDimension()
/ estimateCrop.height();
@@ -557,7 +636,7 @@
if (DEBUG) {
Slog.w(TAG, "Invalid crop dimensions, trying to adjust.");
}
- if (multiCrop) {
+ if (multiCrop()) {
// clear custom crop guidelines, fallback to system default
wallpaper.mCropHints.clear();
generateCropInternal(wallpaper);
@@ -618,7 +697,7 @@
final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
safeWidth, safeHeight, true);
- if (multiCrop) {
+ if (multiCrop()) {
wallpaper.mSampleSize =
((float) cropHint.height()) / finalCrop.getHeight();
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 02594d2..b792f79 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -172,11 +172,6 @@
SparseArray<Rect> mCropHints = new SparseArray<>();
/**
- * cropHints will be ignored if this flag is false
- */
- boolean mSupportsMultiCrop;
-
- /**
* The phone orientation when the wallpaper was set. Only relevant for image wallpapers
*/
int mOrientationWhenSet = ORIENTATION_UNKNOWN;
@@ -204,7 +199,6 @@
if (source.mCropHints != null) {
this.mCropHints = source.mCropHints.clone();
}
- this.mSupportsMultiCrop = source.mSupportsMultiCrop;
this.allowBackup = source.allowBackup;
this.primaryColors = source.primaryColors;
this.mWallpaperDimAmount = source.mWallpaperDimAmount;
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 7f53ea3..4aefb54 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -324,10 +324,7 @@
getAttributeInt(parser, "totalCropTop", 0),
getAttributeInt(parser, "totalCropRight", 0),
getAttributeInt(parser, "totalCropBottom", 0));
- wallpaper.mSupportsMultiCrop = multiCrop() && (
- parser.getAttributeBoolean(null, "supportsMultiCrop", false)
- || mImageWallpaper.equals(wallpaper.wallpaperComponent));
- if (wallpaper.mSupportsMultiCrop) {
+ if (multiCrop() && mImageWallpaper.equals(wallpaper.nextWallpaperComponent)) {
wallpaper.mCropHints = new SparseArray<>();
for (Pair<Integer, String> pair: screenDimensionPairs()) {
Rect cropHint = new Rect(
@@ -342,16 +339,14 @@
}
if (wallpaper.mCropHints.size() == 0 && totalCropHint.isEmpty()) {
// migration case: the crops per screen orientation are not specified.
- int orientation = legacyCropHint.width() < legacyCropHint.height()
- ? WallpaperManager.PORTRAIT : WallpaperManager.LANDSCAPE;
if (!legacyCropHint.isEmpty()) {
- wallpaper.mCropHints.put(orientation, legacyCropHint);
+ wallpaper.cropHint.set(legacyCropHint);
}
} else {
wallpaper.cropHint.set(totalCropHint);
}
wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
- } else {
+ } else if (!multiCrop()) {
wallpaper.cropHint.set(legacyCropHint);
}
final DisplayData wpData = mWallpaperDisplayHelper
@@ -467,13 +462,12 @@
out.startTag(null, tag);
out.attributeInt(null, "id", wallpaper.wallpaperId);
- out.attributeBoolean(null, "supportsMultiCrop", wallpaper.mSupportsMultiCrop);
-
- if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+ if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
if (wallpaper.mCropHints == null) {
Slog.e(TAG, "cropHints should not be null when saved");
wallpaper.mCropHints = new SparseArray<>();
}
+ Rect rectToPutInLegacyCrop = new Rect(wallpaper.cropHint);
for (Pair<Integer, String> pair : screenDimensionPairs()) {
Rect cropHint = wallpaper.mCropHints.get(pair.first);
if (cropHint == null) continue;
@@ -493,12 +487,14 @@
}
}
if (pair.first == orientationToPutInLegacyCrop) {
- out.attributeInt(null, "cropLeft", cropHint.left);
- out.attributeInt(null, "cropTop", cropHint.top);
- out.attributeInt(null, "cropRight", cropHint.right);
- out.attributeInt(null, "cropBottom", cropHint.bottom);
+ rectToPutInLegacyCrop.set(cropHint);
}
}
+ out.attributeInt(null, "cropLeft", rectToPutInLegacyCrop.left);
+ out.attributeInt(null, "cropTop", rectToPutInLegacyCrop.top);
+ out.attributeInt(null, "cropRight", rectToPutInLegacyCrop.right);
+ out.attributeInt(null, "cropBottom", rectToPutInLegacyCrop.bottom);
+
out.attributeInt(null, "totalCropLeft", wallpaper.cropHint.left);
out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 885baf6..802f196 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -42,6 +42,7 @@
import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
import static com.android.window.flags.Flags.multiCrop;
+import static com.android.window.flags.Flags.offloadColorExtraction;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -380,7 +381,7 @@
}
// Outside of the lock since it will synchronize itself
- notifyWallpaperColorsChanged(wallpaper);
+ if (!offloadColorExtraction()) notifyWallpaperColorsChanged(wallpaper);
}
@Override
@@ -403,12 +404,16 @@
}
void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper) {
+ notifyWallpaperColorsChanged(wallpaper, wallpaper.mWhich);
+ }
+
+ private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
if (DEBUG) {
Slog.i(TAG, "Notifying wallpaper colors changed");
}
if (wallpaper.connection != null) {
wallpaper.connection.forEachDisplayConnector(connector ->
- notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId));
+ notifyWallpaperColorsChangedOnDisplay(wallpaper, connector.mDisplayId, which));
}
}
@@ -425,6 +430,11 @@
private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper,
int displayId) {
+ notifyWallpaperColorsChangedOnDisplay(wallpaper, displayId, wallpaper.mWhich);
+ }
+
+ private void notifyWallpaperColorsChangedOnDisplay(@NonNull WallpaperData wallpaper,
+ int displayId, int which) {
boolean needsExtraction;
synchronized (mLock) {
final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
@@ -449,8 +459,8 @@
notify = extractColors(wallpaper);
}
if (notify) {
- notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper),
- wallpaper.mWhich, wallpaper.userId, displayId);
+ notifyColorListeners(getAdjustedWallpaperColorsOnDimming(wallpaper), which,
+ wallpaper.userId, displayId);
}
}
@@ -504,6 +514,7 @@
* @return true unless the wallpaper changed during the color computation
*/
private boolean extractColors(WallpaperData wallpaper) {
+ if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.wallpaperComponent);
String cropFile = null;
boolean defaultImageWallpaper = false;
int wallpaperId;
@@ -803,7 +814,7 @@
null /* options */);
mWindowManagerInternal.setWallpaperShowWhenLocked(
mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
- if (multiCrop() && wallpaper.mSupportsMultiCrop) {
+ if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
mWindowManagerInternal.setWallpaperCropHints(mToken,
mWallpaperCropper.getRelativeCropHints(wallpaper));
} else {
@@ -1148,10 +1159,16 @@
synchronized (mLock) {
// Do not broadcast changes on ImageWallpaper since it's handled
// internally by this class.
- if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) {
+ boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.wallpaperComponent);
+ if (isImageWallpaper && (!offloadColorExtraction() || primaryColors == null)) {
return;
}
mWallpaper.primaryColors = primaryColors;
+ // only save the colors for ImageWallpaper - for live wallpapers, the colors
+ // are always recomputed after a reboot.
+ if (offloadColorExtraction() && isImageWallpaper) {
+ saveSettingsLocked(mWallpaper.userId);
+ }
}
notifyWallpaperColorsChangedOnDisplay(mWallpaper, displayId);
}
@@ -1177,7 +1194,9 @@
try {
// This will trigger onComputeColors in the wallpaper engine.
// It's fine to be locked in here since the binder is oneway.
- connector.mEngine.requestWallpaperColors();
+ if (!offloadColorExtraction() || mWallpaper.primaryColors == null) {
+ connector.mEngine.requestWallpaperColors();
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Failed to request wallpaper colors", e);
}
@@ -1811,6 +1830,7 @@
// Offload color extraction to another thread since switchUser will be called
// from the main thread.
FgThread.getHandler().post(() -> {
+ if (offloadColorExtraction()) return;
notifyWallpaperColorsChanged(systemWallpaper);
if (lockWallpaper != systemWallpaper) notifyWallpaperColorsChanged(lockWallpaper);
notifyWallpaperColorsChanged(mFallbackWallpaper);
@@ -1917,11 +1937,17 @@
final ComponentName component;
final int finalWhich;
- if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) {
- clearWallpaperBitmaps(lockWallpaper);
- }
- if ((which & FLAG_SYSTEM) > 0) {
- clearWallpaperBitmaps(wallpaper);
+ // Clear any previous ImageWallpaper related fields
+ List<WallpaperData> toClear = new ArrayList<>();
+ if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) toClear.add(lockWallpaper);
+ if ((which & FLAG_SYSTEM) > 0) toClear.add(wallpaper);
+ for (WallpaperData wallpaperToClear : toClear) {
+ clearWallpaperBitmaps(wallpaperToClear);
+ if (multiCrop()) {
+ wallpaperToClear.mCropHints.clear();
+ wallpaperToClear.cropHint.set(0, 0, 0, 0);
+ wallpaperToClear.mSampleSize = 1;
+ }
}
// lock only case: set the system wallpaper component to both screens
@@ -2225,7 +2251,9 @@
checkPermission(READ_WALLPAPER_INTERNAL);
WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
: mWallpaperMap.get(userId);
- if (wallpaper == null || !wallpaper.mSupportsMultiCrop) return null;
+ if (wallpaper == null || !mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
+ return null;
+ }
SparseArray<Rect> relativeSuggestedCrops =
mWallpaperCropper.getRelativeCropHints(wallpaper);
Point croppedBitmapSize = new Point(
@@ -2255,7 +2283,7 @@
@Override
public List<Rect> getFutureBitmapCrops(Point bitmapSize, List<Point> displaySizes,
int[] screenOrientations, List<Rect> crops) {
- SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+ SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops);
SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
List<Rect> result = new ArrayList<>();
boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
@@ -2272,7 +2300,7 @@
throw new UnsupportedOperationException(
"This method should only be called with the multi crop flag enabled");
}
- SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops, ORIENTATION_UNKNOWN);
+ SparseArray<Rect> cropMap = getCropMap(screenOrientations, crops);
SparseArray<Rect> defaultCrops = mWallpaperCropper.getDefaultCrops(cropMap, bitmapSize);
return WallpaperCropper.getTotalCrop(defaultCrops);
}
@@ -2714,8 +2742,10 @@
});
// Need to extract colors again to re-calculate dark hints after
// applying dimming.
- wp.mIsColorExtractedFromDim = true;
- pendingColorExtraction.add(wp);
+ if (!offloadColorExtraction()) {
+ wp.mIsColorExtractedFromDim = true;
+ pendingColorExtraction.add(wp);
+ }
changed = true;
}
}
@@ -2724,7 +2754,7 @@
}
}
for (WallpaperData wp: pendingColorExtraction) {
- notifyWallpaperColorsChanged(wp);
+ if (!offloadColorExtraction()) notifyWallpaperColorsChanged(wp);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -2860,10 +2890,8 @@
return null;
}
- int currentOrientation = mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation();
- SparseArray<Rect> cropMap = !multiCrop() ? null
- : getCropMap(screenOrientations, crops, currentOrientation);
- Rect cropHint = multiCrop() || crops == null ? null : crops.get(0);
+ SparseArray<Rect> cropMap = !multiCrop() ? null : getCropMap(screenOrientations, crops);
+ Rect cropHint = multiCrop() || crops == null || crops.isEmpty() ? new Rect() : crops.get(0);
final boolean fromForegroundApp = !multiCrop() ? false
: isFromForegroundApp(callingPackage);
@@ -2912,12 +2940,16 @@
wallpaper.setComplete = completion;
wallpaper.fromForegroundApp = multiCrop() ? fromForegroundApp
: isFromForegroundApp(callingPackage);
- if (!multiCrop()) wallpaper.cropHint.set(cropHint);
- if (multiCrop()) wallpaper.mSupportsMultiCrop = true;
- if (multiCrop()) wallpaper.mCropHints = cropMap;
+ wallpaper.cropHint.set(cropHint);
+ if (multiCrop()) {
+ wallpaper.mCropHints = cropMap;
+ wallpaper.mSampleSize = 1f;
+ wallpaper.mOrientationWhenSet =
+ mWallpaperDisplayHelper.getDefaultDisplayCurrentOrientation();
+ }
wallpaper.allowBackup = allowBackup;
wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
- wallpaper.mOrientationWhenSet = currentOrientation;
+ if (offloadColorExtraction()) wallpaper.primaryColors = null;
}
return pfd;
} finally {
@@ -2926,16 +2958,14 @@
}
}
- private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops,
- int currentOrientation) {
+ private SparseArray<Rect> getCropMap(int[] screenOrientations, List<Rect> crops) {
if ((crops == null ^ screenOrientations == null)
|| (crops != null && crops.size() != screenOrientations.length)) {
throw new IllegalArgumentException(
"Illegal crops/orientations lists: must both be null, or both the same size");
}
SparseArray<Rect> cropMap = new SparseArray<>();
- boolean unknown = false;
- if (crops != null && crops.size() != 0) {
+ if (crops != null && !crops.isEmpty()) {
for (int i = 0; i < crops.size(); i++) {
Rect crop = crops.get(i);
int width = crop.width(), height = crop.height();
@@ -2943,22 +2973,13 @@
throw new IllegalArgumentException("Invalid crop rect supplied: " + crop);
}
int orientation = screenOrientations[i];
- if (orientation == ORIENTATION_UNKNOWN) {
- if (currentOrientation == ORIENTATION_UNKNOWN) {
- throw new IllegalArgumentException(
- "Invalid orientation: " + ORIENTATION_UNKNOWN);
- }
- unknown = true;
- orientation = currentOrientation;
+ if (orientation == ORIENTATION_UNKNOWN && cropMap.size() > 1) {
+ throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN"
+ + "screen orientation should only be used in a singleton map");
}
cropMap.put(orientation, crop);
}
}
- if (unknown && cropMap.size() > 1) {
- throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN screen "
- + "orientation should only be used in a singleton map (in which case it"
- + "represents the current orientation of the default display)");
- }
return cropMap;
}
@@ -2975,7 +2996,6 @@
WallpaperData lockWP = new WallpaperData(userId, FLAG_LOCK);
lockWP.wallpaperId = sysWP.wallpaperId;
lockWP.cropHint.set(sysWP.cropHint);
- lockWP.mSupportsMultiCrop = sysWP.mSupportsMultiCrop;
if (sysWP.mCropHints != null) {
lockWP.mCropHints = sysWP.mCropHints.clone();
}
@@ -3072,6 +3092,10 @@
checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
boolean shouldNotifyColors = false;
+
+ // If the lockscreen wallpaper is set to the same as the home screen, notify that the
+ // lockscreen wallpaper colors changed, even if we don't bind a new wallpaper engine.
+ boolean shouldNotifyLockscreenColors = false;
boolean bindSuccess;
final WallpaperData newWallpaper;
@@ -3096,7 +3120,6 @@
final long ident = Binder.clearCallingIdentity();
try {
- newWallpaper.mSupportsMultiCrop = mImageWallpaper.equals(name);
newWallpaper.imageWallpaperPending = false;
newWallpaper.mWhich = which;
newWallpaper.mSystemWasBoth = systemIsBoth;
@@ -3118,7 +3141,7 @@
bindSuccess = bindWallpaperComponentLocked(name, /* force */
forceRebind, /* fromUser */ true, newWallpaper, reply);
if (bindSuccess) {
- if (!same) {
+ if (!same || (offloadColorExtraction() && forceRebind)) {
newWallpaper.primaryColors = null;
} else {
if (newWallpaper.connection != null) {
@@ -3142,6 +3165,11 @@
newWallpaper.wallpaperId = makeWallpaperIdLocked();
notifyCallbacksLocked(newWallpaper);
shouldNotifyColors = true;
+ if (offloadColorExtraction()) {
+ shouldNotifyColors = false;
+ shouldNotifyLockscreenColors = !force && same && !systemIsBoth
+ && which == (FLAG_SYSTEM | FLAG_LOCK);
+ }
if (which == (FLAG_SYSTEM | FLAG_LOCK)) {
if (DEBUG) {
@@ -3170,6 +3198,10 @@
if (shouldNotifyColors) {
notifyWallpaperColorsChanged(newWallpaper);
}
+ if (shouldNotifyLockscreenColors) {
+ notifyWallpaperColorsChanged(newWallpaper, FLAG_LOCK);
+ }
+
return bindSuccess;
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5184e49..c2b9128 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -96,7 +96,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
-import static com.android.server.wm.WindowManagerInternal.KeyguardExitAnimationStartListener;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_NONE;
@@ -105,7 +104,6 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
-import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -117,7 +115,6 @@
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Pair;
import android.util.Slog;
@@ -137,6 +134,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.TransitionAnimation;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.DumpUtils.Dump;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -244,7 +242,7 @@
mHandler = new Handler(service.mH.getLooper());
mDisplayContent = displayContent;
mTransitionAnimation = new TransitionAnimation(
- context, ProtoLog.isEnabled(WM_DEBUG_ANIM), TAG);
+ context, ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG), TAG);
final TypedArray windowStyle = mContext.getTheme().obtainStyledAttributes(
com.android.internal.R.styleable.Window);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index f6681c5..f7b4a67 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -20,6 +20,7 @@
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -779,6 +780,10 @@
&& wc.asTaskFragment() == null) {
continue;
}
+ // Only care if visibility changed.
+ if (targets.get(i).getTransitMode(wc) == TRANSIT_CHANGE) {
+ continue;
+ }
// WC can be visible due to setLaunchBehind
if (wc.isVisibleRequested()) {
mTmpOpenApps.add(wc);
@@ -843,14 +848,14 @@
* @param targets The final animation targets derived in transition.
* @param finishedTransition The finished transition target.
*/
- boolean onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
+ void onTransitionFinish(ArrayList<Transition.ChangeInfo> targets,
@NonNull Transition finishedTransition) {
if (finishedTransition == mWaitTransitionFinish) {
clearBackAnimations();
}
if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) {
- return false;
+ return;
}
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Handling the deferred animation after transition finished");
@@ -878,7 +883,7 @@
+ " open: " + Arrays.toString(mPendingAnimationBuilder.mOpenTargets)
+ " close: " + mPendingAnimationBuilder.mCloseTarget);
cancelPendingAnimation();
- return false;
+ return;
}
// Ensure the final animation targets which hidden by transition could be visible.
@@ -887,9 +892,14 @@
wc.prepareSurfaces();
}
- scheduleAnimation(mPendingAnimationBuilder);
- mPendingAnimationBuilder = null;
- return true;
+ // The pending builder could be cleared due to prepareSurfaces
+ // => updateNonSystemOverlayWindowsVisibilityIfNeeded
+ // => setForceHideNonSystemOverlayWindowIfNeeded
+ // => updateFocusedWindowLocked => onFocusWindowChanged.
+ if (mPendingAnimationBuilder != null) {
+ scheduleAnimation(mPendingAnimationBuilder);
+ mPendingAnimationBuilder = null;
+ }
}
private void cancelPendingAnimation() {
@@ -1552,15 +1562,17 @@
return this;
}
+ // WC must be Activity/TaskFragment/Task
boolean containTarget(@NonNull WindowContainer wc) {
if (mOpenTargets != null) {
for (int i = mOpenTargets.length - 1; i >= 0; --i) {
- if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)) {
+ if (wc == mOpenTargets[i] || mOpenTargets[i].hasChild(wc)
+ || wc.hasChild(mOpenTargets[i])) {
return true;
}
}
}
- return wc == mCloseTarget || mCloseTarget.hasChild(wc);
+ return wc == mCloseTarget || mCloseTarget.hasChild(wc) || wc.hasChild(mCloseTarget);
}
/**
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 47f4a66..0e446b8 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -38,9 +38,11 @@
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
+import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
import static com.android.window.flags.Flags.balRequireOptInSameUid;
+import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
import static com.android.window.flags.Flags.balShowToastsBlocked;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -539,6 +541,16 @@
sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
sb.append("; resultIfPiSenderAllowsBal: ").append(mResultForRealCaller);
}
+ // features
+ sb.append("; balImproveRealCallerVisibilityCheck: ")
+ .append(balImproveRealCallerVisibilityCheck());
+ sb.append("; balRequireOptInByPendingIntentCreator: ")
+ .append(balRequireOptInByPendingIntentCreator());
+ sb.append("; balRequireOptInSameUid: ").append(balRequireOptInSameUid());
+ sb.append("; balRespectAppSwitchStateWhenCheckBoundByForegroundUid: ")
+ .append(balRespectAppSwitchStateWhenCheckBoundByForegroundUid());
+ sb.append("; balDontBringExistingBackgroundTaskStackToFg: ")
+ .append(balDontBringExistingBackgroundTaskStackToFg());
sb.append("]");
return sb.toString();
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index b8bb258..0ad601d 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -61,6 +61,7 @@
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
@@ -225,13 +226,16 @@
if (keyguardShowing) {
state.mDismissalRequested = false;
}
- if (goingAwayRemoved) {
- // Keyguard dismiss is canceled. Send a transition to undo the changes and clean up
- // before holding the sleep token again.
+ if (goingAwayRemoved || (keyguardShowing && Flags.keyguardAppearTransition())) {
+ // Keyguard decided to show or stopped going away. Send a transition to animate back
+ // to the locked state before holding the sleep token again
final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
dc.requestTransitionAndLegacyPrepare(
TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
- mWindowManager.executeAppTransition();
+ if (Flags.keyguardAppearTransition()) {
+ dc.mWallpaperController.adjustWallpaperWindows();
+ }
+ dc.executeAppTransition();
}
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 3ef6eeb..63bbb31 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -44,6 +44,7 @@
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FastPrintWriter;
import com.android.server.wm.SurfaceAnimator.AnimationType;
@@ -209,7 +210,7 @@
Slog.e(TAG, "Failed to start remote animation", e);
onAnimationFinished();
}
- if (ProtoLog.isEnabled(WM_DEBUG_REMOTE_ANIMATIONS)) {
+ if (ProtoLog.isEnabled(WM_DEBUG_REMOTE_ANIMATIONS, LogLevel.DEBUG)) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation(): Notify animation start:");
writeStartDebugStatement();
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index d67684c..c632714 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -32,6 +32,7 @@
import android.view.SurfaceControl.Transaction;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter;
@@ -192,7 +193,7 @@
return;
}
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
- if (ProtoLog.isEnabled(WM_DEBUG_ANIM)) {
+ if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG)) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
mAnimation.dump(pw, "");
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index a2f6fb4..59bda54 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -22,7 +22,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
@@ -858,10 +857,6 @@
}
public void updateWallpaperTokens(boolean keyguardLocked) {
- if (DEBUG_WALLPAPER) {
- Slog.v(TAG, "Wallpaper vis: target " + mWallpaperTarget + " prev="
- + mPrevWallpaperTarget);
- }
updateWallpaperTokens(mWallpaperTarget != null || mPrevWallpaperTarget != null,
keyguardLocked);
}
@@ -870,6 +865,8 @@
* Change the visibility of the top wallpaper to {@param visibility} and hide all the others.
*/
private void updateWallpaperTokens(boolean visibility, boolean keyguardLocked) {
+ ProtoLog.v(WM_DEBUG_WALLPAPER, "updateWallpaperTokens requestedVisibility=%b on"
+ + " keyguardLocked=%b", visibility, keyguardLocked);
WindowState topWallpaper = mFindResults.getTopWallpaper(keyguardLocked);
WallpaperWindowToken topWallpaperToken =
topWallpaper == null ? null : topWallpaper.mToken.asWallpaperToken();
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 55eeaf2..5c24eee 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -274,6 +274,12 @@
}
@Override
+ boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
+ // TODO(b/233286785): Support sync state for wallpaper. See WindowState#prepareSync.
+ return !mVisibleRequested || !hasVisibleNotDrawnWallpaper();
+ }
+
+ @Override
public String toString() {
if (stringName == null) {
StringBuilder sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 80889d1..90ac576 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -113,6 +113,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.wm.SurfaceAnimator.Animatable;
@@ -3405,7 +3406,7 @@
// ActivityOption#makeCustomAnimation or WindowManager#overridePendingTransition.
a.restrictDuration(MAX_APP_TRANSITION_DURATION);
}
- if (ProtoLog.isEnabled(WM_DEBUG_ANIM)) {
+ if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.DEBUG)) {
ProtoLog.i(WM_DEBUG_ANIM, "Loaded animation %s for %s, duration: %d, stack=%s",
a, this, ((a != null) ? a.getDuration() : 0), Debug.getCallers(20));
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 2e72121..8a68afb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3685,12 +3685,6 @@
// Called by window manager policy. Not exposed externally.
@Override
- public void switchKeyboardLayout(int deviceId, int direction) {
- mInputManager.switchKeyboardLayout(deviceId, direction);
- }
-
- // Called by window manager policy. Not exposed externally.
- @Override
public void shutdown(boolean confirm) {
// Pass in the UI context, since ShutdownThread requires it (to show UI).
ShutdownThread.shutdown(ActivityThread.currentActivityThread().getSystemUiContext(),
@@ -10168,10 +10162,12 @@
// TODO(b/323580163): Check if already shown and update shown state.
if (mSensitiveContentPackages.shouldBlockScreenCaptureForApp(w.getOwningPackage(),
w.getOwningUid(), w.getWindowToken())) {
- Toast.makeText(mContext, Looper.getMainLooper(),
- mContext.getString(R.string.screen_not_shared_sensitive_content),
- Toast.LENGTH_SHORT)
- .show();
+ mH.post(() -> {
+ Toast.makeText(mContext, Looper.getMainLooper(),
+ mContext.getString(R.string.screen_not_shared_sensitive_content),
+ Toast.LENGTH_SHORT)
+ .show();
+ });
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 6ac2774..aac567c 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -202,6 +202,12 @@
// Whether this process has ever started a service with the BIND_INPUT_METHOD permission.
private volatile boolean mHasImeService;
+ /**
+ * Whether this process can use realtime prioirity (SCHED_FIFO) for its UI and render threads
+ * when this process is SCHED_GROUP_TOP_APP.
+ */
+ private final boolean mUseFifoUiScheduling;
+
/** Whether {@link #mActivities} is not empty. */
private volatile boolean mHasActivities;
/** All activities running in the process (exclude destroying). */
@@ -340,6 +346,8 @@
// TODO(b/151161907): Remove after support for display-independent (raw) SysUi configs.
mIsActivityConfigOverrideAllowed = false;
}
+ mUseFifoUiScheduling = com.android.window.flags.Flags.fifoPriorityForMajorUiProcesses()
+ && (isSysUiPackage || mAtm.isCallerRecents(uid));
mCanUseSystemGrammaticalGender = mAtm.mGrammaticalManagerInternal != null
&& mAtm.mGrammaticalManagerInternal.canGetSystemGrammaticalGender(mUid,
@@ -1901,6 +1909,11 @@
}
}
+ /** Returns {@code true} if the process prefers to use fifo scheduling. */
+ public boolean useFifoUiScheduling() {
+ return mUseFifoUiScheduling;
+ }
+
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
public void onTopProcChanged() {
if (mAtm.mVrController.isInterestingToSchedGroup()) {
@@ -2078,6 +2091,9 @@
}
pw.println();
}
+ if (mUseFifoUiScheduling) {
+ pw.println(prefix + " mUseFifoUiScheduling=true");
+ }
final int stateFlags = mActivityStateFlags;
if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b716dc6..7a0245b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -247,6 +247,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.KeyInterceptionInfo;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.ToBooleanFunction;
@@ -4739,7 +4740,7 @@
}
void onExitAnimationDone() {
- if (ProtoLog.isEnabled(WM_DEBUG_ANIM)) {
+ if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.VERBOSE)) {
final AnimationAdapter animationAdapter = mSurfaceAnimator.getAnimation();
StringWriter sw = new StringWriter();
if (animationAdapter != null) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index a242d42..6fd7aa0 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -61,6 +61,7 @@
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.policy.WindowManagerPolicy;
@@ -586,7 +587,7 @@
mWin.mAttrs, attr, TRANSIT_OLD_NONE);
}
}
- if (ProtoLog.isEnabled(WM_DEBUG_ANIM)) {
+ if (ProtoLog.isEnabled(WM_DEBUG_ANIM, LogLevel.VERBOSE)) {
ProtoLog.v(WM_DEBUG_ANIM, "applyAnimation: win=%s"
+ " anim=%d attr=0x%x a=%s transit=%d type=%d isEntrance=%b Callers %s",
this, anim, attr, a, transit, mAttrType, isEntrance, Debug.getCallers(20));
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 8598023..f1962cb 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -226,11 +226,8 @@
<xs:element name="transitionPoint" type="nonNegativeDecimal" minOccurs="1"
maxOccurs="1">
</xs:element>
- <xs:element name="nits" type="xs:float" maxOccurs="unbounded">
- </xs:element>
- <xs:element name="backlight" type="xs:float" maxOccurs="unbounded">
- </xs:element>
- <xs:element name="brightness" type="xs:float" maxOccurs="unbounded">
+ <!-- Mapping of nits -> backlight -> brightness -->
+ <xs:element name="brightnessMapping" type="comprehensiveBrightnessMap" maxOccurs="1">
</xs:element>
<!-- Mapping of current lux to minimum allowed nits values. -->
<xs:element name="luxToMinimumNitsMap" type="nitsMap" maxOccurs="1">
@@ -449,6 +446,35 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="comprehensiveBrightnessMap">
+ <xs:sequence>
+ <xs:element name="brightnessPoint" type="brightnessPoint" maxOccurs="unbounded" minOccurs="2">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ <!-- valid value of interpolation if specified: linear -->
+ <xs:attribute name="interpolation" type="xs:string" use="optional"/>
+ </xs:complexType>
+
+ <xs:complexType name="brightnessPoint">
+ <xs:sequence>
+ <xs:element type="nonNegativeDecimal" name="nits">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element type="nonNegativeDecimal" name="backlight">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <xs:element type="nonNegativeDecimal" name="brightness">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+
+
<xs:complexType name="sdrHdrRatioMap">
<xs:sequence>
<xs:element name="point" type="sdrHdrRatioPoint" maxOccurs="unbounded" minOccurs="2">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 4ce4cc3..170434c 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -53,6 +53,16 @@
method public final void setType(@NonNull com.android.server.display.config.PredefinedBrightnessLimitNames);
}
+ public class BrightnessPoint {
+ ctor public BrightnessPoint();
+ method @NonNull public final java.math.BigDecimal getBacklight();
+ method @NonNull public final java.math.BigDecimal getBrightness();
+ method @NonNull public final java.math.BigDecimal getNits();
+ method public final void setBacklight(@NonNull java.math.BigDecimal);
+ method public final void setBrightness(@NonNull java.math.BigDecimal);
+ method public final void setNits(@NonNull java.math.BigDecimal);
+ }
+
public class BrightnessThresholds {
ctor public BrightnessThresholds();
method public final com.android.server.display.config.ThresholdPoints getBrightnessThresholdPoints();
@@ -76,6 +86,13 @@
method public final void setThermalStatus(@NonNull com.android.server.display.config.ThermalStatus);
}
+ public class ComprehensiveBrightnessMap {
+ ctor public ComprehensiveBrightnessMap();
+ method @NonNull public final java.util.List<com.android.server.display.config.BrightnessPoint> getBrightnessPoint();
+ method public String getInterpolation();
+ method public void setInterpolation(String);
+ }
+
public class Density {
ctor public Density();
method @NonNull public final java.math.BigInteger getDensity();
@@ -183,12 +200,11 @@
public class EvenDimmerMode {
ctor public EvenDimmerMode();
- method public java.util.List<java.lang.Float> getBacklight();
- method public java.util.List<java.lang.Float> getBrightness();
+ method public com.android.server.display.config.ComprehensiveBrightnessMap getBrightnessMapping();
method public boolean getEnabled();
method public com.android.server.display.config.NitsMap getLuxToMinimumNitsMap();
- method public java.util.List<java.lang.Float> getNits();
method public java.math.BigDecimal getTransitionPoint();
+ method public void setBrightnessMapping(com.android.server.display.config.ComprehensiveBrightnessMap);
method public void setEnabled(boolean);
method public void setLuxToMinimumNitsMap(com.android.server.display.config.NitsMap);
method public void setTransitionPoint(java.math.BigDecimal);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 3dd7b54..1dd719e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -12875,7 +12875,7 @@
Slogf.i(LOG_TAG, "Stopping user %d", userId);
final long id = mInjector.binderClearCallingIdentity();
try {
- switch (mInjector.getIActivityManager().stopUser(userId, true /*force*/, null)) {
+ switch (mInjector.getIActivityManager().stopUserWithCallback(userId, null)) {
case ActivityManager.USER_OP_SUCCESS:
return UserManager.USER_OPERATION_SUCCESS;
case ActivityManager.USER_OP_IS_CURRENT:
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
index 6ad8d79..6393e11 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
@@ -1,6 +1,7 @@
aconfig_declarations {
name: "device_state_flags",
package: "com.android.server.policy.feature.flags",
+ container: "system",
srcs: [
"device_state_flags.aconfig",
],
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
index 29e258c..21e33dd 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.policy.feature.flags"
+container: "system"
flag {
name: "enable_dual_display_blocking"
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 0a7f49d..3c6b500 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -108,29 +108,50 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accounts.AccountManagerService;
import com.android.server.adaptiveauth.AdaptiveAuthService;
+import com.android.server.adb.AdbService;
+import com.android.server.alarm.AlarmManagerService;
import com.android.server.am.ActivityManagerService;
+import com.android.server.ambientcontext.AmbientContextManagerService;
+import com.android.server.app.GameManagerService;
import com.android.server.appbinding.AppBindingService;
+import com.android.server.apphibernation.AppHibernationService;
import com.android.server.appop.AppOpMigrationHelper;
import com.android.server.appop.AppOpMigrationHelperImpl;
+import com.android.server.appprediction.AppPredictionManagerService;
+import com.android.server.appwidget.AppWidgetService;
import com.android.server.art.ArtModuleServiceInitializer;
import com.android.server.art.DexUseManagerLocal;
import com.android.server.attention.AttentionManagerService;
import com.android.server.audio.AudioService;
+import com.android.server.autofill.AutofillManagerService;
+import com.android.server.backup.BackupManagerService;
import com.android.server.biometrics.AuthService;
import com.android.server.biometrics.BiometricService;
import com.android.server.biometrics.sensors.face.FaceService;
import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
import com.android.server.biometrics.sensors.iris.IrisService;
+import com.android.server.blob.BlobStoreManagerService;
import com.android.server.broadcastradio.BroadcastRadioService;
import com.android.server.camera.CameraServiceProxy;
import com.android.server.clipboard.ClipboardService;
+import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.virtual.VirtualDeviceManagerService;
import com.android.server.compat.PlatformCompat;
import com.android.server.compat.PlatformCompatNative;
+import com.android.server.compat.overrides.AppCompatOverridesService;
+import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.PacProxyService;
+import com.android.server.content.ContentService;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
+import com.android.server.contentcapture.ContentCaptureManagerService;
+import com.android.server.contentsuggestions.ContentSuggestionsManagerService;
+import com.android.server.contextualsearch.ContextualSearchManagerService;
import com.android.server.coverage.CoverageService;
import com.android.server.cpu.CpuMonitorService;
+import com.android.server.credentials.CredentialManagerService;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.devicestate.DeviceStateManagerService;
@@ -147,14 +168,20 @@
import com.android.server.input.InputManagerService;
import com.android.server.inputmethod.InputMethodManagerService;
import com.android.server.integrity.AppIntegrityManagerService;
+import com.android.server.job.JobSchedulerService;
import com.android.server.lights.LightsService;
import com.android.server.locales.LocaleManagerService;
import com.android.server.location.LocationManagerService;
import com.android.server.location.altitude.AltitudeService;
+import com.android.server.locksettings.LockSettingsService;
import com.android.server.logcat.LogcatManagerService;
+import com.android.server.media.MediaResourceMonitorService;
import com.android.server.media.MediaRouterService;
+import com.android.server.media.MediaSessionService;
import com.android.server.media.metrics.MediaMetricsManagerService;
import com.android.server.media.projection.MediaProjectionManagerService;
+import com.android.server.midi.MidiService;
+import com.android.server.musicrecognition.MusicRecognitionManagerService;
import com.android.server.net.NetworkManagementService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.watchlist.NetworkWatchlistService;
@@ -195,12 +222,16 @@
import com.android.server.power.ThermalManagerService;
import com.android.server.power.hint.HintManagerService;
import com.android.server.powerstats.PowerStatsService;
+import com.android.server.print.PrintManagerService;
import com.android.server.profcollect.ProfcollectForwardingService;
import com.android.server.recoverysystem.RecoverySystemService;
import com.android.server.resources.ResourcesManagerService;
import com.android.server.restrictions.RestrictionsManagerService;
import com.android.server.role.RoleServicePlatformHelper;
+import com.android.server.rollback.RollbackManagerService;
import com.android.server.rotationresolver.RotationResolverManagerService;
+import com.android.server.search.SearchManagerService;
+import com.android.server.searchui.SearchUiManagerService;
import com.android.server.security.AttestationVerificationManagerService;
import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
@@ -210,16 +241,28 @@
import com.android.server.sensorprivacy.SensorPrivacyService;
import com.android.server.sensors.SensorService;
import com.android.server.signedconfig.SignedConfigService;
+import com.android.server.slice.SliceManagerService;
+import com.android.server.smartspace.SmartspaceManagerService;
import com.android.server.soundtrigger.SoundTriggerService;
import com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService;
+import com.android.server.speech.SpeechRecognitionManagerService;
+import com.android.server.stats.bootstrap.StatsBootstrapAtomService;
+import com.android.server.stats.pull.StatsPullAtomService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
+import com.android.server.systemcaptions.SystemCaptionsManagerService;
import com.android.server.telecom.TelecomLoaderService;
import com.android.server.testharness.TestHarnessModeService;
import com.android.server.textclassifier.TextClassificationManagerService;
import com.android.server.textservices.TextServicesManagerService;
+import com.android.server.texttospeech.TextToSpeechManagerService;
+import com.android.server.timedetector.GnssTimeUpdateService;
import com.android.server.timedetector.NetworkTimeUpdateService;
+import com.android.server.timedetector.TimeDetectorService;
+import com.android.server.timezonedetector.TimeZoneDetectorService;
+import com.android.server.timezonedetector.location.LocationTimeZoneManagerService;
import com.android.server.tracing.TracingServiceProxy;
+import com.android.server.translation.TranslationManagerService;
import com.android.server.trust.TrustManagerService;
import com.android.server.tv.TvInputManagerService;
import com.android.server.tv.TvRemoteService;
@@ -227,10 +270,15 @@
import com.android.server.tv.tunerresourcemanager.TunerResourceManagerService;
import com.android.server.twilight.TwilightService;
import com.android.server.uri.UriGrantsManagerService;
+import com.android.server.usage.StorageStatsService;
import com.android.server.usage.UsageStatsService;
+import com.android.server.usb.UsbService;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.vibrator.VibratorManagerService;
+import com.android.server.voiceinteraction.VoiceInteractionManagerService;
import com.android.server.vr.VrManagerService;
+import com.android.server.wallpaper.WallpaperManagerService;
+import com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService;
import com.android.server.wearable.WearableSensingManagerService;
import com.android.server.webkit.WebViewUpdateService;
import com.android.server.wm.ActivityTaskManagerService;
@@ -268,44 +316,20 @@
* Implementation class names. TODO: Move them to a codegen class or load
* them from the build system somehow.
*/
- private static final String BACKUP_MANAGER_SERVICE_CLASS =
- "com.android.server.backup.BackupManagerService$Lifecycle";
- private static final String APPWIDGET_SERVICE_CLASS =
- "com.android.server.appwidget.AppWidgetService";
private static final String ARC_NETWORK_SERVICE_CLASS =
"com.android.server.arc.net.ArcNetworkService";
private static final String ARC_PERSISTENT_DATA_BLOCK_SERVICE_CLASS =
"com.android.server.arc.persistent_data_block.ArcPersistentDataBlockService";
private static final String ARC_SYSTEM_HEALTH_SERVICE =
"com.android.server.arc.health.ArcSystemHealthService";
- private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS =
- "com.android.server.voiceinteraction.VoiceInteractionManagerService";
- private static final String APP_HIBERNATION_SERVICE_CLASS =
- "com.android.server.apphibernation.AppHibernationService";
- private static final String PRINT_MANAGER_SERVICE_CLASS =
- "com.android.server.print.PrintManagerService";
- private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
- "com.android.server.companion.CompanionDeviceManagerService";
- private static final String VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS =
- "com.android.server.companion.virtual.VirtualDeviceManagerService";
private static final String STATS_COMPANION_APEX_PATH =
"/apex/com.android.os.statsd/javalib/service-statsd.jar";
+ private static final String STATS_COMPANION_LIFECYCLE_CLASS =
+ "com.android.server.stats.StatsCompanion$Lifecycle";
private static final String SCHEDULING_APEX_PATH =
"/apex/com.android.scheduling/javalib/service-scheduling.jar";
private static final String REBOOT_READINESS_LIFECYCLE_CLASS =
"com.android.server.scheduling.RebootReadinessManagerService$Lifecycle";
- private static final String CONNECTIVITY_SERVICE_APEX_PATH =
- "/apex/com.android.tethering/javalib/service-connectivity.jar";
- private static final String STATS_COMPANION_LIFECYCLE_CLASS =
- "com.android.server.stats.StatsCompanion$Lifecycle";
- private static final String STATS_PULL_ATOM_SERVICE_CLASS =
- "com.android.server.stats.pull.StatsPullAtomService";
- private static final String STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS =
- "com.android.server.stats.bootstrap.StatsBootstrapAtomService$Lifecycle";
- private static final String USB_SERVICE_CLASS =
- "com.android.server.usb.UsbService$Lifecycle";
- private static final String MIDI_SERVICE_CLASS =
- "com.android.server.midi.MidiService$Lifecycle";
private static final String WIFI_APEX_SERVICE_JAR_PATH =
"/apex/com.android.wifi/javalib/service-wifi.jar";
private static final String WIFI_SERVICE_CLASS =
@@ -320,16 +344,6 @@
"com.android.server.wifi.p2p.WifiP2pService";
private static final String LOWPAN_SERVICE_CLASS =
"com.android.server.lowpan.LowpanService";
- private static final String JOB_SCHEDULER_SERVICE_CLASS =
- "com.android.server.job.JobSchedulerService";
- private static final String LOCK_SETTINGS_SERVICE_CLASS =
- "com.android.server.locksettings.LockSettingsService$Lifecycle";
- private static final String STORAGE_MANAGER_SERVICE_CLASS =
- "com.android.server.StorageManagerService$Lifecycle";
- private static final String STORAGE_STATS_SERVICE_CLASS =
- "com.android.server.usage.StorageStatsService$Lifecycle";
- private static final String SEARCH_MANAGER_SERVICE_CLASS =
- "com.android.server.search.SearchManagerService$Lifecycle";
private static final String THERMAL_OBSERVER_CLASS =
"com.android.clockwork.ThermalObserver";
private static final String WEAR_CONNECTIVITY_SERVICE_CLASS =
@@ -354,91 +368,26 @@
"com.android.clockwork.settings.WearSettingsService";
private static final String WRIST_ORIENTATION_SERVICE_CLASS =
"com.android.clockwork.wristorientation.WristOrientationService";
- private static final String ACCOUNT_SERVICE_CLASS =
- "com.android.server.accounts.AccountManagerService$Lifecycle";
- private static final String CONTENT_SERVICE_CLASS =
- "com.android.server.content.ContentService$Lifecycle";
- private static final String WALLPAPER_SERVICE_CLASS =
- "com.android.server.wallpaper.WallpaperManagerService$Lifecycle";
- private static final String AUTO_FILL_MANAGER_SERVICE_CLASS =
- "com.android.server.autofill.AutofillManagerService";
- private static final String CREDENTIAL_MANAGER_SERVICE_CLASS =
- "com.android.server.credentials.CredentialManagerService";
- private static final String CONTENT_CAPTURE_MANAGER_SERVICE_CLASS =
- "com.android.server.contentcapture.ContentCaptureManagerService";
- private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
- "com.android.server.translation.TranslationManagerService";
- private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
- "com.android.server.musicrecognition.MusicRecognitionManagerService";
- private static final String AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS =
- "com.android.server.ambientcontext.AmbientContextManagerService";
- private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
- "com.android.server.systemcaptions.SystemCaptionsManagerService";
- private static final String TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS =
- "com.android.server.texttospeech.TextToSpeechManagerService";
+
private static final String IOT_SERVICE_CLASS =
"com.android.things.server.IoTSystemService";
- private static final String SLICE_MANAGER_SERVICE_CLASS =
- "com.android.server.slice.SliceManagerService$Lifecycle";
private static final String CAR_SERVICE_HELPER_SERVICE_CLASS =
"com.android.internal.car.CarServiceHelperService";
- private static final String TIME_DETECTOR_SERVICE_CLASS =
- "com.android.server.timedetector.TimeDetectorService$Lifecycle";
- private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS =
- "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle";
- private static final String LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS =
- "com.android.server.timezonedetector.location.LocationTimeZoneManagerService$Lifecycle";
- private static final String GNSS_TIME_UPDATE_SERVICE_CLASS =
- "com.android.server.timedetector.GnssTimeUpdateService$Lifecycle";
- private static final String ACCESSIBILITY_MANAGER_SERVICE_CLASS =
- "com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
- private static final String ADB_SERVICE_CLASS =
- "com.android.server.adb.AdbService$Lifecycle";
- private static final String SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS =
- "com.android.server.speech.SpeechRecognitionManagerService";
- private static final String WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS =
- "com.android.server.wallpapereffectsgeneration.WallpaperEffectsGenerationManagerService";
- private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
- "com.android.server.appprediction.AppPredictionManagerService";
- private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
- "com.android.server.contentsuggestions.ContentSuggestionsManagerService";
- private static final String SEARCH_UI_MANAGER_SERVICE_CLASS =
- "com.android.server.searchui.SearchUiManagerService";
- private static final String SMARTSPACE_MANAGER_SERVICE_CLASS =
- "com.android.server.smartspace.SmartspaceManagerService";
- private static final String CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS =
- "com.android.server.contextualsearch.ContextualSearchManagerService";
- private static final String DEVICE_IDLE_CONTROLLER_CLASS =
- "com.android.server.DeviceIdleController";
- private static final String BLOB_STORE_MANAGER_SERVICE_CLASS =
- "com.android.server.blob.BlobStoreManagerService";
private static final String APPSEARCH_MODULE_LIFECYCLE_CLASS =
"com.android.server.appsearch.AppSearchModule$Lifecycle";
private static final String ISOLATED_COMPILATION_SERVICE_CLASS =
"com.android.server.compos.IsolatedCompilationService";
- private static final String ROLLBACK_MANAGER_SERVICE_CLASS =
- "com.android.server.rollback.RollbackManagerService";
- private static final String ALARM_MANAGER_SERVICE_CLASS =
- "com.android.server.alarm.AlarmManagerService";
- private static final String MEDIA_SESSION_SERVICE_CLASS =
- "com.android.server.media.MediaSessionService";
- private static final String MEDIA_RESOURCE_MONITOR_SERVICE_CLASS =
- "com.android.server.media.MediaResourceMonitorService";
+ private static final String CONNECTIVITY_SERVICE_APEX_PATH =
+ "/apex/com.android.tethering/javalib/service-connectivity.jar";
private static final String CONNECTIVITY_SERVICE_INITIALIZER_CLASS =
"com.android.server.ConnectivityServiceInitializer";
private static final String NETWORK_STATS_SERVICE_INITIALIZER_CLASS =
"com.android.server.NetworkStatsServiceInitializer";
- private static final String IP_CONNECTIVITY_METRICS_CLASS =
- "com.android.server.connectivity.IpConnectivityMetrics";
private static final String MEDIA_COMMUNICATION_SERVICE_CLASS =
"com.android.server.media.MediaCommunicationService";
- private static final String APP_COMPAT_OVERRIDES_SERVICE_CLASS =
- "com.android.server.compat.overrides.AppCompatOverridesService$Lifecycle";
private static final String HEALTHCONNECT_MANAGER_SERVICE_CLASS =
"com.android.server.healthconnect.HealthConnectManagerService";
private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
- private static final String GAME_MANAGER_SERVICE_CLASS =
- "com.android.server.app.GameManagerService$Lifecycle";
private static final String ENHANCED_CONFIRMATION_SERVICE_CLASS =
"com.android.ecm.EnhancedConfirmationService";
@@ -461,6 +410,7 @@
+ "OnDevicePersonalizationSystemService$Lifecycle";
private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
"com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
+
private static final String DEVICE_LOCK_SERVICE_CLASS =
"com.android.server.devicelock.DeviceLockService";
private static final String DEVICE_LOCK_APEX_PATH =
@@ -1435,7 +1385,7 @@
// Manages apk rollbacks.
t.traceBegin("StartRollbackManagerService");
- mSystemServiceManager.startService(ROLLBACK_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(RollbackManagerService.class);
t.traceEnd();
// Tracks native tombstones.
@@ -1580,11 +1530,11 @@
// The AccountManager must come before the ContentService
t.traceBegin("StartAccountManagerService");
- mSystemServiceManager.startService(ACCOUNT_SERVICE_CLASS);
+ mSystemServiceManager.startService(AccountManagerService.Lifecycle.class);
t.traceEnd();
t.traceBegin("StartContentService");
- mSystemServiceManager.startService(CONTENT_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContentService.Lifecycle.class);
t.traceEnd();
t.traceBegin("InstallSystemProviders");
@@ -1639,7 +1589,7 @@
// TODO(aml-jobscheduler): Think about how to do it properly.
t.traceBegin("StartAlarmManagerService");
- mSystemServiceManager.startService(ALARM_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AlarmManagerService.class);
t.traceEnd();
t.traceBegin("StartInputManagerService");
@@ -1721,7 +1671,7 @@
}
t.traceBegin("IpConnectivityMetrics");
- mSystemServiceManager.startService(IP_CONNECTIVITY_METRICS_CLASS);
+ mSystemServiceManager.startService(IpConnectivityMetrics.class);
t.traceEnd();
t.traceBegin("NetworkWatchlistService");
@@ -1796,7 +1746,7 @@
t.traceBegin("StartAccessibilityManagerService");
try {
- mSystemServiceManager.startService(ACCESSIBILITY_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AccessibilityManagerService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting Accessibility Manager", e);
}
@@ -1819,7 +1769,7 @@
* NotificationManagerService is dependant on StorageManagerService,
* (for media / usb notifications) so we must start StorageManagerService first.
*/
- mSystemServiceManager.startService(STORAGE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(StorageManagerService.Lifecycle.class);
storageManager = IStorageManager.Stub.asInterface(
ServiceManager.getService("mount"));
} catch (Throwable e) {
@@ -1829,7 +1779,7 @@
t.traceBegin("StartStorageStatsService");
try {
- mSystemServiceManager.startService(STORAGE_STATS_SERVICE_CLASS);
+ mSystemServiceManager.startService(StorageStatsService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting StorageStatsService", e);
}
@@ -1860,7 +1810,7 @@
t.traceEnd();
t.traceBegin("StartAppHibernationService");
- mSystemServiceManager.startService(APP_HIBERNATION_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppHibernationService.class);
t.traceEnd();
t.traceBegin("ArtManagerLocal");
@@ -1892,7 +1842,7 @@
} else {
t.traceBegin("StartLockSettingsService");
try {
- mSystemServiceManager.startService(LOCK_SETTINGS_SERVICE_CLASS);
+ mSystemServiceManager.startService(LockSettingsService.Lifecycle.class);
lockSettings = ILockSettings.Stub.asInterface(
ServiceManager.getService("lock_settings"));
} catch (Throwable e) {
@@ -1925,7 +1875,7 @@
}
t.traceBegin("StartDeviceIdleController");
- mSystemServiceManager.startService(DEVICE_IDLE_CONTROLLER_CLASS);
+ mSystemServiceManager.startService(DeviceIdleController.class);
t.traceEnd();
// Always start the Device Policy Manager, so that the API is compatible with
@@ -1948,7 +1898,7 @@
if (deviceHasConfigString(context,
R.string.config_defaultMusicRecognitionService)) {
t.traceBegin("StartMusicRecognitionManagerService");
- mSystemServiceManager.startService(MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(MusicRecognitionManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG,
@@ -1966,7 +1916,7 @@
if (deviceHasConfigString(
context, R.string.config_defaultAmbientContextDetectionService)) {
t.traceBegin("StartAmbientContextService");
- mSystemServiceManager.startService(AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AmbientContextManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "AmbientContextManagerService not defined by OEM or disabled by flag");
@@ -1974,13 +1924,13 @@
// System Speech Recognition Service
t.traceBegin("StartSpeechRecognitionManagerService");
- mSystemServiceManager.startService(SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SpeechRecognitionManagerService.class);
t.traceEnd();
// App prediction manager service
if (deviceHasConfigString(context, R.string.config_defaultAppPredictionService)) {
t.traceBegin("StartAppPredictionService");
- mSystemServiceManager.startService(APP_PREDICTION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppPredictionManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "AppPredictionService not defined by OEM");
@@ -1989,7 +1939,7 @@
// Content suggestions manager service
if (deviceHasConfigString(context, R.string.config_defaultContentSuggestionsService)) {
t.traceBegin("StartContentSuggestionsService");
- mSystemServiceManager.startService(CONTENT_SUGGESTIONS_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContentSuggestionsManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "ContentSuggestionsService not defined by OEM");
@@ -1998,14 +1948,14 @@
// Search UI manager service
if (deviceHasConfigString(context, R.string.config_defaultSearchUiService)) {
t.traceBegin("StartSearchUiService");
- mSystemServiceManager.startService(SEARCH_UI_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SearchUiManagerService.class);
t.traceEnd();
}
// Smartspace manager service
if (deviceHasConfigString(context, R.string.config_defaultSmartspaceService)) {
t.traceBegin("StartSmartspaceService");
- mSystemServiceManager.startService(SMARTSPACE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SmartspaceManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "SmartspaceManagerService not defined by OEM or disabled by flag");
@@ -2015,7 +1965,7 @@
if (deviceHasConfigString(context,
R.string.config_defaultContextualSearchPackageName)) {
t.traceBegin("StartContextualSearchService");
- mSystemServiceManager.startService(CONTEXTUAL_SEARCH_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContextualSearchManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "ContextualSearchManagerService not defined or disabled by flag");
@@ -2214,7 +2164,7 @@
t.traceBegin("StartTimeDetectorService");
try {
- mSystemServiceManager.startService(TIME_DETECTOR_SERVICE_CLASS);
+ mSystemServiceManager.startService(TimeDetectorService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting TimeDetectorService service", e);
}
@@ -2235,7 +2185,7 @@
t.traceBegin("StartTimeZoneDetectorService");
try {
- mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS);
+ mSystemServiceManager.startService(TimeZoneDetectorService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting TimeZoneDetectorService service", e);
}
@@ -2251,7 +2201,7 @@
t.traceBegin("StartLocationTimeZoneManagerService");
try {
- mSystemServiceManager.startService(LOCATION_TIME_ZONE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(LocationTimeZoneManagerService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting LocationTimeZoneManagerService service", e);
}
@@ -2260,7 +2210,7 @@
if (context.getResources().getBoolean(R.bool.config_enableGnssTimeUpdateService)) {
t.traceBegin("StartGnssTimeUpdateService");
try {
- mSystemServiceManager.startService(GNSS_TIME_UPDATE_SERVICE_CLASS);
+ mSystemServiceManager.startService(GnssTimeUpdateService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting GnssTimeUpdateService service", e);
}
@@ -2270,7 +2220,7 @@
if (!isWatch) {
t.traceBegin("StartSearchManagerService");
try {
- mSystemServiceManager.startService(SEARCH_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SearchManagerService.Lifecycle.class);
} catch (Throwable e) {
reportWtf("starting Search Service", e);
}
@@ -2279,7 +2229,7 @@
if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {
t.traceBegin("StartWallpaperManagerService");
- mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
+ mSystemServiceManager.startService(WallpaperManagerService.Lifecycle.class);
t.traceEnd();
} else {
Slog.i(TAG, "Wallpaper service disabled by config");
@@ -2289,8 +2239,7 @@
if (deviceHasConfigString(context,
R.string.config_defaultWallpaperEffectsGenerationService)) {
t.traceBegin("StartWallpaperEffectsGenerationService");
- mSystemServiceManager.startService(
- WALLPAPER_EFFECTS_GENERATION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(WallpaperEffectsGenerationManagerService.class);
t.traceEnd();
}
@@ -2345,14 +2294,14 @@
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
// Start MIDI Manager service
t.traceBegin("StartMidiManager");
- mSystemServiceManager.startService(MIDI_SERVICE_CLASS);
+ mSystemServiceManager.startService(MidiService.Lifecycle.class);
t.traceEnd();
}
// Start ADB Debugging Service
t.traceBegin("StartAdbService");
try {
- mSystemServiceManager.startService(ADB_SERVICE_CLASS);
+ mSystemServiceManager.startService(AdbService.Lifecycle.class);
} catch (Throwable e) {
Slog.e(TAG, "Failure starting AdbService");
}
@@ -2364,7 +2313,7 @@
|| Build.IS_EMULATOR) {
// Manage USB host and device support
t.traceBegin("StartUsbService");
- mSystemServiceManager.startService(USB_SERVICE_CLASS);
+ mSystemServiceManager.startService(UsbService.Lifecycle.class);
t.traceEnd();
}
@@ -2396,7 +2345,7 @@
// TODO(aml-jobscheduler): Think about how to do it properly.
t.traceBegin("StartJobScheduler");
- mSystemServiceManager.startService(JOB_SCHEDULER_SERVICE_CLASS);
+ mSystemServiceManager.startService(JobSchedulerService.class);
t.traceEnd();
t.traceBegin("StartSoundTrigger");
@@ -2409,14 +2358,14 @@
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
t.traceBegin("StartBackupManager");
- mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(BackupManagerService.Lifecycle.class);
t.traceEnd();
}
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
|| context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
t.traceBegin("StartAppWidgetService");
- mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppWidgetService.class);
t.traceEnd();
}
@@ -2425,7 +2374,7 @@
// of initializing various settings. It will internally modify its behavior
// based on that feature.
t.traceBegin("StartVoiceRecognitionManager");
- mSystemServiceManager.startService(VOICE_RECOGNITION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(VoiceInteractionManagerService.class);
t.traceEnd();
if (GestureLauncherService.isGestureLauncherEnabled(context.getResources())) {
@@ -2486,7 +2435,7 @@
}
t.traceBegin(START_BLOB_STORE_SERVICE);
- mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(BlobStoreManagerService.class);
t.traceEnd();
// Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
@@ -2507,7 +2456,7 @@
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
t.traceBegin("StartPrintManager");
- mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(PrintManagerService.class);
t.traceEnd();
}
@@ -2517,13 +2466,13 @@
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)) {
t.traceBegin("StartCompanionDeviceManager");
- mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(CompanionDeviceManagerService.class);
t.traceEnd();
}
if (context.getResources().getBoolean(R.bool.config_enableVirtualDeviceManager)) {
t.traceBegin("StartVirtualDeviceManager");
- mSystemServiceManager.startService(VIRTUAL_DEVICE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(VirtualDeviceManagerService.class);
t.traceEnd();
}
@@ -2532,7 +2481,7 @@
t.traceEnd();
t.traceBegin("StartMediaSessionService");
- mSystemServiceManager.startService(MEDIA_SESSION_SERVICE_CLASS);
+ mSystemServiceManager.startService(MediaSessionService.class);
t.traceEnd();
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
@@ -2563,7 +2512,7 @@
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
t.traceBegin("StartMediaResourceMonitor");
- mSystemServiceManager.startService(MEDIA_RESOURCE_MONITOR_SERVICE_CLASS);
+ mSystemServiceManager.startService(MediaResourceMonitorService.class);
t.traceEnd();
}
@@ -2735,7 +2684,7 @@
if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) {
t.traceBegin("StartSliceManagerService");
- mSystemServiceManager.startService(SLICE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SliceManagerService.Lifecycle.class);
t.traceEnd();
}
@@ -2759,12 +2708,12 @@
// Statsd pulled atoms
t.traceBegin("StartStatsPullAtomService");
- mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS);
+ mSystemServiceManager.startService(StatsPullAtomService.class);
t.traceEnd();
// Log atoms to statsd from bootstrap processes.
t.traceBegin("StatsBootstrapAtomService");
- mSystemServiceManager.startService(STATS_BOOTSTRAP_ATOM_SERVICE_LIFECYCLE_CLASS);
+ mSystemServiceManager.startService(StatsBootstrapAtomService.Lifecycle.class);
t.traceEnd();
// Incidentd and dumpstated helper
@@ -2811,7 +2760,7 @@
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOFILL)) {
t.traceBegin("StartAutoFillService");
- mSystemServiceManager.startService(AUTO_FILL_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(AutofillManagerService.class);
t.traceEnd();
}
@@ -2820,13 +2769,12 @@
DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CREDENTIAL,
CredentialManager.DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
if (credentialManagerEnabled) {
- if(isWatch &&
- !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
- Slog.d(TAG, "CredentialManager disabled on wear.");
+ if (isWatch && !android.credentials.flags.Flags.wearCredentialManagerEnabled()) {
+ Slog.d(TAG, "CredentialManager disabled on wear.");
} else {
- t.traceBegin("StartCredentialManagerService");
- mSystemServiceManager.startService(CREDENTIAL_MANAGER_SERVICE_CLASS);
- t.traceEnd();
+ t.traceBegin("StartCredentialManagerService");
+ mSystemServiceManager.startService(CredentialManagerService.class);
+ t.traceEnd();
}
} else {
Slog.d(TAG, "CredentialManager disabled.");
@@ -2836,7 +2784,7 @@
// Translation manager service
if (deviceHasConfigString(context, R.string.config_defaultTranslationService)) {
t.traceBegin("StartTranslationManagerService");
- mSystemServiceManager.startService(TRANSLATION_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(TranslationManagerService.class);
t.traceEnd();
} else {
Slog.d(TAG, "TranslationService not defined by OEM");
@@ -2980,7 +2928,7 @@
t.traceEnd();
t.traceBegin("GameManagerService");
- mSystemServiceManager.startService(GAME_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(GameManagerService.Lifecycle.class);
t.traceEnd();
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)) {
@@ -3012,7 +2960,7 @@
t.traceEnd();
t.traceBegin("AppCompatOverridesService");
- mSystemServiceManager.startService(APP_COMPAT_OVERRIDES_SERVICE_CLASS);
+ mSystemServiceManager.startService(AppCompatOverridesService.Lifecycle.class);
t.traceEnd();
t.traceBegin("HealthConnectManagerService");
@@ -3397,14 +3345,14 @@
}
t.traceBegin("StartSystemCaptionsManagerService");
- mSystemServiceManager.startService(SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(SystemCaptionsManagerService.class);
t.traceEnd();
}
private void startTextToSpeechManagerService(@NonNull Context context,
@NonNull TimingsTraceAndSlog t) {
t.traceBegin("StartTextToSpeechManagerService");
- mSystemServiceManager.startService(TEXT_TO_SPEECH_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(TextToSpeechManagerService.class);
t.traceEnd();
}
@@ -3439,7 +3387,7 @@
}
t.traceBegin("StartContentCaptureService");
- mSystemServiceManager.startService(CONTENT_CAPTURE_MANAGER_SERVICE_CLASS);
+ mSystemServiceManager.startService(ContentCaptureManagerService.class);
ContentCaptureManagerInternal ccmi =
LocalServices.getService(ContentCaptureManagerInternal.class);
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 36bea7d..3675834 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -16,6 +16,7 @@
package com.android.server.permission.access
+import android.permission.flags.Flags
import android.util.Slog
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
@@ -78,6 +79,9 @@
setPackageStates(packageStates)
setDisabledSystemPackageStates(disabledSystemPackageStates)
packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
mutateAppIdPackageNames()
.mutateOrPut(packageState.appId) { MutableIndexedListSet() }
.add(packageState.packageName)
@@ -103,6 +107,9 @@
newState.mutateUserStatesNoWrite()[userId] = MutableUserState()
forEachSchemePolicy { with(it) { onUserAdded(userId) } }
newState.externalState.packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
upgradePackageVersion(packageState, userId)
}
}
@@ -126,6 +133,9 @@
setPackageStates(packageStates)
setDisabledSystemPackageStates(disabledSystemPackageStates)
packageStates.forEach { (packageName, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
if (packageState.volumeUuid == volumeUuid) {
// The APK for a package on a mounted storage volume may still be unavailable
// due to APK being deleted, e.g. after an OTA.
@@ -151,6 +161,9 @@
with(it) { onStorageVolumeMounted(volumeUuid, packageNames, isSystemUpdated) }
}
packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
if (packageState.volumeUuid == volumeUuid) {
newState.userStates.forEachIndexed { _, userId, _ ->
upgradePackageVersion(packageState, userId)
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 47fd970..63fb468 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -81,6 +81,9 @@
override fun MutateStateScope.onUserAdded(userId: Int) {
newState.externalState.packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
}
newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ ->
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index b32c544..44ed3df 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1445,6 +1445,9 @@
val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates }
service.mutateState {
packageStates.forEach { (packageName, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
val androidPackage = packageState.androidPackage ?: return@forEach
androidPackage.requestedPermissions.forEach { permissionName ->
updatePermissionFlags(
@@ -1598,7 +1601,7 @@
) {
with(policy) { getPermissionFlags(appId, userId, permissionName) }
} else {
- if (permissionName !in DEVICE_AWARE_PERMISSIONS) {
+ if (permissionName !in PermissionManager.DEVICE_AWARE_PERMISSIONS) {
Slog.i(
LOG_TAG,
"$permissionName is not device aware permission, " +
@@ -1623,7 +1626,7 @@
) {
with(policy) { setPermissionFlags(appId, userId, permissionName, flags) }
} else {
- if (permissionName !in DEVICE_AWARE_PERMISSIONS) {
+ if (permissionName !in PermissionManager.DEVICE_AWARE_PERMISSIONS) {
Slog.i(
LOG_TAG,
"$permissionName is not device aware permission, " +
@@ -1877,6 +1880,9 @@
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
service.mutateState {
snapshot.packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
with(policy) { resetRuntimePermissions(packageState.packageName, userId) }
with(devicePolicy) { resetRuntimePermissions(packageState.packageName, userId) }
}
@@ -1918,8 +1924,11 @@
}
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
- snapshot.packageStates.forEach packageStates@{ (_, packageState) ->
- val androidPackage = packageState.androidPackage ?: return@packageStates
+ snapshot.packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
+ val androidPackage = packageState.androidPackage ?: return@forEach
if (permissionName in androidPackage.requestedPermissions) {
packageNames += androidPackage.packageName
}
@@ -1934,6 +1943,9 @@
val permissions = service.getState { with(policy) { getPermissions() } }
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
snapshot.packageStates.forEach packageStates@{ (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@packageStates
+ }
val androidPackage = packageState.androidPackage ?: return@packageStates
androidPackage.requestedPermissions.forEach requestedPermissions@{ permissionName ->
val permission = permissions[permissionName] ?: return@requestedPermissions
@@ -2060,6 +2072,9 @@
val appIdPackageNames = MutableIndexedMap<Int, MutableIndexedSet<String>>()
packageStates.forEach { (_, packageState) ->
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return@forEach
+ }
appIdPackageNames
.getOrPut(packageState.appId) { MutableIndexedSet() }
.add(packageState.packageName)
@@ -2313,6 +2328,10 @@
isInstantApp: Boolean,
oldPackage: AndroidPackage?
) {
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return
+ }
+
synchronized(storageVolumeLock) {
// Accumulating the package names here because we want to maintain the same call order
// of onPackageAdded() and reuse this order in onStorageVolumeAdded(). We need the
@@ -2339,6 +2358,10 @@
params: PermissionManagerServiceInternal.PackageInstalledParams,
userId: Int
) {
+ if (Flags.ignoreApexPermissions() && androidPackage.isApex) {
+ return
+ }
+
if (params === PermissionManagerServiceInternal.PackageInstalledParams.DEFAULT) {
// TODO: We should actually stop calling onPackageInstalled() when we are passing
// PackageInstalledParams.DEFAULT in InstallPackageHelper, because there's actually no
@@ -2391,6 +2414,10 @@
sharedUserPkgs: List<AndroidPackage>,
userId: Int
) {
+ if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ return
+ }
+
val userIds =
if (userId == UserHandle.USER_ALL) {
userManagerService.userIdsIncludingPreCreated
@@ -2820,15 +2847,6 @@
PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or
PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
- /** These permissions are supported for virtual devices. */
- // TODO: b/298661870 - Use new API to get the list of device aware permissions.
- val DEVICE_AWARE_PERMISSIONS =
- if (Flags.deviceAwarePermissionsEnabled()) {
- setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
- } else {
- emptySet<String>()
- }
-
fun getFullerPermission(permissionName: String): String? =
FULLER_PERMISSIONS[permissionName]
}
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 b80d44f..5897d76 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -924,7 +924,7 @@
getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true));
assertTrue(mDisplayDeviceConfig.isEvenDimmerAvailable());
- assertEquals(0.0001f, mDisplayDeviceConfig.getBacklightFromBrightness(0.1f), ZERO_DELTA);
+ assertEquals(0.01f, mDisplayDeviceConfig.getBacklightFromBrightness(0.002f), ZERO_DELTA);
assertEquals(0.2f, mDisplayDeviceConfig.getNitsFromBacklight(0.0f), ZERO_DELTA);
}
@@ -1649,18 +1649,28 @@
private String evenDimmerConfig(boolean enabled) {
return (enabled ? "<evenDimmer enabled=\"true\">" : "<evenDimmer enabled=\"false\">")
+ " <transitionPoint>0.1</transitionPoint>\n"
- + " <nits>0.2</nits>\n"
- + " <nits>2.0</nits>\n"
- + " <nits>500.0</nits>\n"
- + " <nits>1000.0</nits>\n"
- + " <backlight>0</backlight>\n"
- + " <backlight>0.0001</backlight>\n"
- + " <backlight>0.5</backlight>\n"
- + " <backlight>1.0</backlight>\n"
- + " <brightness>0</brightness>\n"
- + " <brightness>0.1</brightness>\n"
- + " <brightness>0.5</brightness>\n"
- + " <brightness>1.0</brightness>\n"
+ + " <brightnessMapping>\n"
+ + " <brightnessPoint>\n"
+ + " <nits>0.2</nits>\n"
+ + " <backlight>0</backlight>\n"
+ + " <brightness>0</brightness>\n"
+ + " </brightnessPoint>\n"
+ + " <brightnessPoint>\n"
+ + " <nits>2.0</nits>\n"
+ + " <backlight>0.01</backlight>\n"
+ + " <brightness>0.002</brightness>\n"
+ + " </brightnessPoint>\n"
+ + " <brightnessPoint>\n"
+ + " <nits>500.0</nits>\n"
+ + " <backlight>0.5</backlight>\n"
+ + " <brightness>0.5</brightness>\n"
+ + " </brightnessPoint>\n"
+ + " <brightnessPoint>\n"
+ + " <nits>1000</nits>\n"
+ + " <backlight>1.0</backlight>\n"
+ + " <brightness>1.0</brightness>\n"
+ + " </brightnessPoint>\n"
+ + " </brightnessMapping>\n"
+ " <luxToMinimumNitsMap>\n"
+ " <point>\n"
+ " <value>10</value> <nits>0.3</nits>\n"
diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS
index f801560..0eb8639 100644
--- a/services/tests/mockingservicestests/src/com/android/server/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS
@@ -1,5 +1,5 @@
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS
per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS
-per-file SensitiveContentProtectionManagerServiceTest.java = file:/core/java/android/permission/OWNERS
+per-file SensitiveContentProtectionManagerService* = file:/core/java/android/permission/OWNERS
per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
index 5065144..edee8cd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
@@ -25,7 +25,6 @@
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -34,6 +33,7 @@
import android.content.pm.PackageManagerInternal;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
+import android.os.Process;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -79,7 +79,8 @@
private static final String NOTIFICATION_PKG_1 = "com.android.server.notification.one";
private static final String NOTIFICATION_PKG_2 = "com.android.server.notification.two";
- private static final String EXEMPTED_SCREEN_RECORDER_PACKAGE = "test.screen.recorder.package";
+ private static final String SCREEN_RECORDER_PACKAGE = "test.screen.recorder.package";
+ private static final String EXEMPTED_SCREEN_RECORDER_PACKAGE = "exempt.screen.recorder.package";
private static final int NOTIFICATION_UID_1 = 5;
private static final int NOTIFICATION_UID_2 = 6;
@@ -281,10 +282,18 @@
.getActiveNotifications();
}
+ private MediaProjectionInfo createMediaProjectionInfo() {
+ return new MediaProjectionInfo(SCREEN_RECORDER_PACKAGE, Process.myUserHandle(), null);
+ }
+
+ private MediaProjectionInfo createExemptMediaProjectionInfo() {
+ return new MediaProjectionInfo(
+ EXEMPTED_SCREEN_RECORDER_PACKAGE, Process.myUserHandle(), null);
+ }
+
@Test
public void mediaProjectionOnStart_verifyExemptedRecorderPackage() {
- MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
- when(mediaProjectionInfo.getPackageName()).thenReturn(EXEMPTED_SCREEN_RECORDER_PACKAGE);
+ MediaProjectionInfo mediaProjectionInfo = createExemptMediaProjectionInfo();
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
@@ -295,7 +304,7 @@
public void mediaProjectionOnStart_onProjectionStart_setWmBlockedPackages() {
ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@@ -304,18 +313,18 @@
public void mediaProjectionOnStart_noSensitiveNotifications_noBlockedPackages() {
setupNoSensitiveNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
public void mediaProjectionOnStart_noNotifications_noBlockedPackages() {
setupNoNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -323,7 +332,7 @@
ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromSamePackageAndUid();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@@ -333,7 +342,7 @@
ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromDifferentPackage();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@@ -343,7 +352,7 @@
ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromDifferentUid();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@@ -352,7 +361,7 @@
public void mediaProjectionOnStop_onProjectionEnd_clearWmBlockedPackages() {
setupSensitiveNotification();
- MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
+ MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo();
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
Mockito.reset(mWindowManager);
@@ -365,7 +374,7 @@
public void mediaProjectionOnStart_afterOnStop_onProjectionStart_setWmBlockedPackages() {
ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
- MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
+ MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo();
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
Mockito.reset(mWindowManager);
@@ -381,9 +390,9 @@
.when(mSensitiveContentProtectionManagerService.mNotificationListener)
.getActiveNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -392,9 +401,9 @@
.when(mSensitiveContentProtectionManagerService.mNotificationListener)
.getCurrentRanking();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -403,9 +412,9 @@
.when(mSensitiveContentProtectionManagerService.mNotificationListener)
.getCurrentRanking();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -416,9 +425,9 @@
.when(mSensitiveContentProtectionManagerService.mNotificationListener)
.getCurrentRanking();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -426,7 +435,7 @@
mockDisabledViaDevelopOption();
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
verifyZeroInteractions(mWindowManager);
}
@@ -447,8 +456,9 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+ mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
@@ -461,7 +471,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
@@ -472,23 +482,23 @@
@Test
public void nlsOnListenerConnected_noSensitiveNotifications_noBlockedPackages() {
setupNoSensitiveNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
public void nlsOnListenerConnected_noNotifications_noBlockedPackages() {
setupNoNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -496,7 +506,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
doReturn(null)
.when(mSensitiveContentProtectionManagerService.mNotificationListener)
@@ -504,7 +514,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -512,7 +522,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
doReturn(mRankingMap)
@@ -521,7 +531,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -530,7 +540,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
verifyZeroInteractions(mWindowManager);
@@ -553,8 +563,9 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+ mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -568,7 +579,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -580,25 +591,25 @@
@Test
public void nlsOnNotificationRankingUpdate_noSensitiveNotifications_noBlockedPackages() {
setupNoSensitiveNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
public void nlsOnNotificationRankingUpdate_noNotifications_noBlockedPackages() {
setupNoNotifications();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -606,13 +617,13 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(null);
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -620,7 +631,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
doReturn(mRankingMap)
@@ -630,7 +641,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -638,7 +649,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
doThrow(SecurityException.class)
@@ -648,7 +659,7 @@
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
- verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ verifyZeroInteractions(mWindowManager);
}
@Test
@@ -657,7 +668,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationRankingUpdate(mRankingMap);
@@ -681,8 +692,9 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ MediaProjectionInfo mediaProjectionInfo = createMediaProjectionInfo();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+ mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -696,7 +708,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -712,7 +724,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -726,7 +738,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -740,7 +752,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
mSensitiveContentProtectionManagerService.mNotificationListener
@@ -754,7 +766,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
Mockito.reset(mWindowManager);
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
@@ -770,7 +782,7 @@
// Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
// as non-sensitive
setupSensitiveNotification();
- mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo());
mSensitiveContentProtectionManagerService.mNotificationListener
.onNotificationPosted(mNotification1, mRankingMap);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index a7430e5..419bcb8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -38,6 +38,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.am.ActivityManagerInternalTest.CustomThread;
import static com.android.server.am.ActivityManagerService.Injector;
@@ -692,6 +693,31 @@
assertEquals(uid, -1);
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testFifoSwitch() {
+ addUidRecord(TEST_UID, TEST_PACKAGE);
+ final ProcessRecord fifoProc = mAms.getProcessRecordLocked(TEST_PACKAGE, TEST_UID);
+ final var wpc = fifoProc.getWindowProcessController();
+ spyOn(wpc);
+ doReturn(true).when(wpc).useFifoUiScheduling();
+ fifoProc.makeActive(fifoProc.getThread(), mAms.mProcessStats);
+ assertTrue(fifoProc.useFifoUiScheduling());
+ assertTrue(mAms.mSpecifiedFifoProcesses.contains(fifoProc));
+
+ // If there is a request to use more CPU resource (e.g. camera), the current fifo process
+ // should switch the capability of using fifo.
+ final UidRecord uidRecord = addUidRecord(TEST_UID + 1, TEST_PACKAGE + 1);
+ uidRecord.setCurProcState(PROCESS_STATE_TOP);
+ mAms.adjustFifoProcessesIfNeeded(uidRecord.getUid(), false /* allowSpecifiedFifo */);
+ assertFalse(fifoProc.useFifoUiScheduling());
+ mAms.adjustFifoProcessesIfNeeded(uidRecord.getUid(), true /* allowSpecifiedFifo */);
+ assertTrue(fifoProc.useFifoUiScheduling());
+
+ fifoProc.makeInactive(mAms.mProcessStats);
+ assertFalse(mAms.mSpecifiedFifoProcesses.contains(fifoProc));
+ }
+
@Test
public void testGlobalIsolatedUidAllocator() {
final IsolatedUidRange globalUidRange = mAms.mProcessList.mGlobalIsolatedUids;
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index b415682..0532e04 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -55,6 +55,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -63,8 +64,7 @@
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
-import android.security.Authorization;
-import android.security.authorization.IKeystoreAuthorization;
+import android.security.KeyStoreAuthorization;
import android.service.trust.TrustAgentService;
import android.testing.TestableContext;
import android.view.IWindowManager;
@@ -96,7 +96,6 @@
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.spyStatic(ActivityManager.class)
- .spyStatic(Authorization.class)
.mockStatic(ServiceManager.class)
.mockStatic(WindowManagerGlobal.class)
.build();
@@ -126,14 +125,13 @@
private @Mock DevicePolicyManager mDevicePolicyManager;
private @Mock FaceManager mFaceManager;
private @Mock FingerprintManager mFingerprintManager;
- private @Mock IKeystoreAuthorization mKeystoreAuthorization;
+ private @Mock KeyStoreAuthorization mKeyStoreAuthorization;
private @Mock LockPatternUtils mLockPatternUtils;
private @Mock PackageManager mPackageManager;
private @Mock UserManager mUserManager;
private @Mock IWindowManager mWindowManager;
private HandlerThread mHandlerThread;
- private TrustManagerService.Injector mInjector;
private TrustManagerService mService;
private ITrustManager mTrustManager;
@@ -145,8 +143,6 @@
when(mFaceManager.getSensorProperties()).thenReturn(List.of());
when(mFingerprintManager.getSensorProperties()).thenReturn(List.of());
- doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
-
when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
when(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).thenReturn(mKnownTrustAgents);
@@ -193,8 +189,7 @@
mHandlerThread = new HandlerThread("handler");
mHandlerThread.start();
- mInjector = new TrustManagerService.Injector(mLockPatternUtils, mHandlerThread.getLooper());
- mService = new TrustManagerService(mMockContext, mInjector);
+ mService = new TrustManagerService(mMockContext, new MockInjector(mMockContext));
// Get the ITrustManager from the new TrustManagerService.
mService.onStart();
@@ -204,6 +199,27 @@
mTrustManager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
}
+ private class MockInjector extends TrustManagerService.Injector {
+ MockInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }
+
+ @Override
+ KeyStoreAuthorization getKeyStoreAuthorization() {
+ return mKeyStoreAuthorization;
+ }
+
+ @Override
+ Looper getLooper() {
+ return mHandlerThread.getLooper();
+ }
+ }
+
@After
public void tearDown() {
LocalServices.removeServiceForTest(SystemServiceManager.class);
@@ -371,14 +387,14 @@
when(mWindowManager.isKeyguardLocked()).thenReturn(false);
mTrustManager.reportKeyguardShowingChanged();
- verify(mKeystoreAuthorization).onDeviceUnlocked(PARENT_USER_ID, null);
- verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
+ verify(mKeyStoreAuthorization).onDeviceUnlocked(PARENT_USER_ID, null);
+ verify(mKeyStoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mTrustManager.reportKeyguardShowingChanged();
- verify(mKeystoreAuthorization)
+ verify(mKeyStoreAuthorization)
.onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
- verify(mKeystoreAuthorization)
+ verify(mKeyStoreAuthorization)
.onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
}
@@ -392,10 +408,10 @@
setupMocksForProfile(/* unifiedChallenge= */ false);
mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, false);
- verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
+ verify(mKeyStoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
- verify(mKeystoreAuthorization)
+ verify(mKeyStoreAuthorization)
.onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS), eq(false));
}
@@ -572,11 +588,11 @@
private void verifyWeakUnlockValue(boolean expectedWeakUnlockEnabled) throws Exception {
when(mWindowManager.isKeyguardLocked()).thenReturn(false);
mTrustManager.reportKeyguardShowingChanged();
- verify(mKeystoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
+ verify(mKeyStoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mTrustManager.reportKeyguardShowingChanged();
- verify(mKeystoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
+ verify(mKeyStoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
eq(expectedWeakUnlockEnabled));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index 7ecc7fd..29f3720 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -235,12 +235,11 @@
int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
Point expectedCropSize = new Point(expectedWidth, 1000);
for (int mode: ALL_MODES) {
- assertThat(WallpaperCropper.getAdjustedCrop(
- crop, bitmapSize, displaySize, true, false, mode))
- .isEqualTo(leftOf(crop, expectedCropSize));
- assertThat(WallpaperCropper.getAdjustedCrop(
- crop, bitmapSize, displaySize, true, true, mode))
- .isEqualTo(rightOf(crop, expectedCropSize));
+ for (boolean rtl: List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, rtl, mode))
+ .isEqualTo(centerOf(crop, expectedCropSize));
+ }
}
}
@@ -362,11 +361,13 @@
}
/**
- * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when
- * no suggested crops are provided.
+ * Test that {@link WallpaperCropper#getCrop} uses the full image when no crops are provided.
+ * If the image has more width/height ratio than the screen, keep that width for parallax up
+ * to {@link WallpaperCropper#MAX_PARALLAX}. If the crop has less width/height ratio, remove the
+ * surplus height, on both sides to keep the wallpaper centered.
*/
@Test
- public void testGetCrop_noSuggestedCrops_centersWallpaper() {
+ public void testGetCrop_noSuggestedCrops() {
setUpWithDisplays(STANDARD_DISPLAY);
Point bitmapSize = new Point(800, 1000);
Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
@@ -374,9 +375,11 @@
List<Point> displaySizes = List.of(
new Point(500, 1000),
+ new Point(200, 1000),
new Point(1000, 500));
List<Point> expectedCropSizes = List.of(
- new Point(500, 1000),
+ new Point(Math.min(800, (int) (500 * (1 + WallpaperCropper.MAX_PARALLAX))), 1000),
+ new Point(Math.min(800, (int) (200 * (1 + WallpaperCropper.MAX_PARALLAX))), 1000),
new Point(800, 400));
for (int i = 0; i < displaySizes.size(); i++) {
@@ -450,7 +453,8 @@
/**
* Test that {@link WallpaperCropper#getCrop}, when asked for a folded crop with a suggested
* crop only for the relative unfolded orientation, creates the folded crop at the center of the
- * unfolded crop, by removing content on two sides to match the folded screen dimensions.
+ * unfolded crop, by removing content on two sides to match the folded screen dimensions, and
+ * then adds some width for parallax.
* <p>
* To simplify, in this test case all crops have the same size as the display (no zoom)
* and are at the center of the image.
@@ -468,6 +472,7 @@
int unfoldedTwo = getRotatedOrientation(unfoldedOne);
Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne));
Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo));
+ List<Rect> unfoldedCrops = List.of(unfoldedCropOne, unfoldedCropTwo);
SparseArray<Rect> suggestedCrops = new SparseArray<>();
suggestedCrops.put(unfoldedOne, unfoldedCropOne);
suggestedCrops.put(unfoldedTwo, unfoldedCropTwo);
@@ -476,15 +481,28 @@
int foldedTwo = getFoldedOrientation(unfoldedTwo);
Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+ List<Point> foldedDisplays = List.of(foldedDisplayOne, foldedDisplayTwo);
for (boolean rtl : List.of(false, true)) {
- assertThat(mWallpaperCropper.getCrop(
- foldedDisplayOne, bitmapSize, suggestedCrops, rtl))
- .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne));
+ for (int i = 0; i < 2; i++) {
+ Rect unfoldedCrop = unfoldedCrops.get(i);
+ Point foldedDisplay = foldedDisplays.get(i);
+ Rect expectedCrop = centerOf(unfoldedCrop, foldedDisplay);
+ int maxParallax = (int) (WallpaperCropper.MAX_PARALLAX * unfoldedCrop.width());
- assertThat(mWallpaperCropper.getCrop(
- foldedDisplayTwo, bitmapSize, suggestedCrops, rtl))
- .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo));
+ // the expected behaviour is that we add width for parallax until we reach
+ // either MAX_PARALLAX or the edge of the crop for the unfolded screen.
+ if (rtl) {
+ expectedCrop.left = Math.max(
+ unfoldedCrop.left, expectedCrop.left - maxParallax);
+ } else {
+ expectedCrop.right = Math.min(
+ unfoldedCrop.right, unfoldedCrop.right + maxParallax);
+ }
+ assertThat(mWallpaperCropper.getCrop(
+ foldedDisplay, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(expectedCrop);
+ }
}
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
index 05d8a00..c4561b1 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
@@ -87,7 +87,7 @@
final boolean[] userStopped = new boolean[1];
CountDownLatch stopUserLatch = new CountDownLatch(1);
- mIam.stopUser(mTestUserId, true, new IStopUserCallback.Stub() {
+ mIam.stopUserWithCallback(mTestUserId, new IStopUserCallback.Stub() {
@Override
public void userStopped(int userId) throws RemoteException {
userStopped[0] = true;
diff --git a/services/tests/selinux/AndroidTest.xml b/services/tests/selinux/AndroidTest.xml
new file mode 100644
index 0000000..16d8e07
--- /dev/null
+++ b/services/tests/selinux/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs Selinux Frameworks Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="SelinuxFrameworksTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="SelinuxFrameworksTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.frameworks.selinuxtests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index e0a99b0..e189098 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -3156,6 +3156,39 @@
}
@SmallTest
+ public void testAccountRemovedBroadcastMarkedAccountAsVisibleTwice() throws Exception {
+ unlockSystemUser();
+
+ HashMap<String, Integer> visibility = new HashMap<>();
+ visibility.put("testpackage1", AccountManager.VISIBILITY_USER_MANAGED_VISIBLE);
+
+ addAccountRemovedReceiver("testpackage1");
+ mAms.registerAccountListener(
+ new String [] {AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1},
+ "testpackage1");
+ mAms.addAccountExplicitlyWithVisibility(
+ AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ /* password= */ "p11",
+ /* extras= */ null,
+ visibility,
+ /* callerPackage= */ null);
+
+ updateBroadcastCounters(2);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 1);
+ assertEquals(mLoginAccountsChangedBroadcasts, 1);
+ assertEquals(mAccountRemovedBroadcasts, 0);
+
+ mAms.setAccountVisibility(AccountManagerServiceTestFixtures.ACCOUNT_SUCCESS,
+ "testpackage1",
+ AccountManager.VISIBILITY_VISIBLE);
+
+ updateBroadcastCounters(3);
+ assertEquals(mVisibleAccountsChangedBroadcasts, 1); // visibility was not changed
+ assertEquals(mLoginAccountsChangedBroadcasts, 2);
+ assertEquals(mAccountRemovedBroadcasts, 0);
+ }
+
+ @SmallTest
public void testRegisterAccountListenerCredentialsUpdate() throws Exception {
unlockSystemUser();
mAms.registerAccountListener(
@@ -3493,6 +3526,12 @@
}
@Override
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn,
+ Context.BindServiceFlags flags, UserHandle user) {
+ return mTestContext.bindServiceAsUser(service, conn, flags, user);
+ }
+
+ @Override
public void unbindService(ServiceConnection conn) {
mTestContext.unbindService(conn);
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 0a2a855..7c0dbf4 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -888,7 +888,7 @@
int userId = -1;
assertThrows(IllegalArgumentException.class,
- () -> mUserController.stopUser(userId, /* force= */ true,
+ () -> mUserController.stopUser(userId,
/* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
/* keyEvictedCallback= */ null));
}
@@ -897,7 +897,7 @@
public void testStopUser_systemUser() {
int userId = UserHandle.USER_SYSTEM;
- int r = mUserController.stopUser(userId, /* force= */ true,
+ int r = mUserController.stopUser(userId,
/* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
/* keyEvictedCallback= */ null);
@@ -909,7 +909,7 @@
setUpUser(TEST_USER_ID1, /* flags= */ 0);
mUserController.startUser(TEST_USER_ID1, USER_START_MODE_FOREGROUND);
- int r = mUserController.stopUser(TEST_USER_ID1, /* force= */ true,
+ int r = mUserController.stopUser(TEST_USER_ID1,
/* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
/* keyEvictedCallback= */ null);
@@ -1338,7 +1338,7 @@
private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean allowDelayedLocking,
KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception {
- int r = mUserController.stopUser(userId, /* force= */ true, /* allowDelayedLocking= */
+ int r = mUserController.stopUser(userId, /* allowDelayedLocking= */
allowDelayedLocking, null, keyEvictedCallback);
assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
assertUserLockedOrUnlockedState(userId, allowDelayedLocking, expectLocking);
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index f1c1dc3..59f4d56b 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -24,6 +24,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
@@ -315,7 +316,7 @@
mFakeBtDevice.getAddress()));
verify(mMockAudioService,
timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState(
- eq(devState));
+ eq(devState), anyBoolean());
}
// metadata set
@@ -326,7 +327,7 @@
mFakeBtDevice.getAddress()));
verify(mMockAudioService,
timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState(
- any());
+ any(), anyBoolean());
}
} finally {
// reset the metadata device type
@@ -354,7 +355,7 @@
verify(mMockAudioService,
timeout(MAX_MESSAGE_HANDLING_DELAY_MS).atLeast(1)).onUpdatedAdiDeviceState(
ArgumentMatchers.argThat(devState -> devState.getAudioDeviceCategory()
- == AudioManager.AUDIO_DEVICE_CATEGORY_OTHER));
+ == AudioManager.AUDIO_DEVICE_CATEGORY_OTHER), anyBoolean());
}
private void doTestConnectionDisconnectionReconnection(int delayAfterDisconnection,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 74eb79d..34092b6 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -68,7 +68,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import android.security.KeyStore;
+import android.security.KeyStoreAuthorization;
import androidx.test.filters.SmallTest;
@@ -105,7 +105,7 @@
@Mock private IBiometricServiceReceiver mClientReceiver;
@Mock private IStatusBarService mStatusBarService;
@Mock private IBiometricSysuiReceiver mSysuiReceiver;
- @Mock private KeyStore mKeyStore;
+ @Mock private KeyStoreAuthorization mKeyStoreAuthorization;
@Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
@Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
@Mock private BiometricCameraManager mBiometricCameraManager;
@@ -665,9 +665,10 @@
final PreAuthInfo preAuthInfo = createPreAuthInfo(sensors, userId, promptInfo,
checkDevicePolicyManager);
return new AuthSession(mContext, mBiometricContext, mStatusBarService, mSysuiReceiver,
- mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, mToken, requestId,
- operationId, userId, mSensorReceiver, mClientReceiver, TEST_PACKAGE, promptInfo,
- false /* debugEnabled */, mFingerprintSensorProps, mBiometricFrameworkStatsLogger);
+ mKeyStoreAuthorization, mRandom, mClientDeathReceiver, preAuthInfo, mToken,
+ requestId, operationId, userId, mSensorReceiver, mClientReceiver, TEST_PACKAGE,
+ promptInfo, false /* debugEnabled */, mFingerprintSensorProps,
+ mBiometricFrameworkStatsLogger);
}
private PromptInfo createPromptInfo(@Authenticators.Types int authenticators) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index a852677..5fd29c2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -82,8 +82,7 @@
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.security.GateKeeper;
-import android.security.KeyStore;
-import android.security.authorization.IKeystoreAuthorization;
+import android.security.KeyStoreAuthorization;
import android.service.gatekeeper.IGateKeeperService;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -182,7 +181,7 @@
private BiometricHandlerProvider mBiometricHandlerProvider;
@Mock
- private IKeystoreAuthorization mKeystoreAuthService;
+ private KeyStoreAuthorization mKeyStoreAuthorization;
@Mock
private IGateKeeperService mGateKeeperService;
@@ -207,7 +206,7 @@
when(mInjector.getStatusBarService()).thenReturn(mock(IStatusBarService.class));
when(mInjector.getSettingObserver(any(), any(), any()))
.thenReturn(mock(BiometricService.SettingObserver.class));
- when(mInjector.getKeyStore()).thenReturn(mock(KeyStore.class));
+ when(mInjector.getKeyStoreAuthorization()).thenReturn(mock(KeyStoreAuthorization.class));
when(mInjector.isDebugEnabled(any(), anyInt())).thenReturn(false);
when(mInjector.getBiometricStrengthController(any()))
.thenReturn(mock(BiometricStrengthController.class));
@@ -243,7 +242,7 @@
mStatusBarService, null /* handler */,
mAuthSessionCoordinator);
when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider);
- when(mInjector.getKeystoreAuthorizationService()).thenReturn(mKeystoreAuthService);
+ when(mInjector.getKeyStoreAuthorization()).thenReturn(mKeyStoreAuthorization);
when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
when(mInjector.getNotificationLogger()).thenReturn(mNotificationLogger);
when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
@@ -682,9 +681,9 @@
waitForIdle();
// HAT sent to keystore
if (isStrongBiometric) {
- verify(mBiometricService.mKeyStore).addAuthToken(AdditionalMatchers.aryEq(HAT));
+ verify(mKeyStoreAuthorization).addAuthToken(AdditionalMatchers.aryEq(HAT));
} else {
- verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+ verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
}
// Send onAuthenticated to client
verify(mReceiver1).onAuthenticationSucceeded(
@@ -747,7 +746,7 @@
waitForIdle();
// Waiting for SystemUI to send confirmation callback
assertEquals(STATE_AUTH_PENDING_CONFIRM, mBiometricService.mAuthSession.getState());
- verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+ verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
// SystemUI sends confirm, HAT is sent to keystore and client is notified.
mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed(
@@ -755,9 +754,9 @@
null /* credentialAttestation */);
waitForIdle();
if (isStrongBiometric) {
- verify(mBiometricService.mKeyStore).addAuthToken(AdditionalMatchers.aryEq(HAT));
+ verify(mKeyStoreAuthorization).addAuthToken(AdditionalMatchers.aryEq(HAT));
} else {
- verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+ verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
}
verify(mReceiver1).onAuthenticationSucceeded(
BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
@@ -1313,7 +1312,7 @@
eq(TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
- verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class));
+ verify(mKeyStoreAuthorization, never()).addAuthToken(any(byte[].class));
assertNull(mBiometricService.mAuthSession);
}
@@ -1839,7 +1838,7 @@
final long expectedResult = 31337L;
- when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
+ when(mKeyStoreAuthorization.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)))
.thenReturn(expectedResult);
mBiometricService = new BiometricService(mContext, mInjector, mBiometricHandlerProvider);
@@ -1848,7 +1847,8 @@
Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
assertEquals(expectedResult, result);
- verify(mKeystoreAuthService).getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators));
+ verify(mKeyStoreAuthorization).getLastAuthTime(eq(secureUserId),
+ eq(hardwareAuthenticators));
}
// Helper methods
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index fa89278..44aa868 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -30,6 +30,9 @@
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.system.Os;
import android.text.FontConfig;
import android.util.Xml;
@@ -38,8 +41,11 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.text.flags.Flags;
+
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParser;
@@ -69,6 +75,9 @@
private static final String LEGACY_FONTS_XML = "/system/etc/fonts.xml";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
/**
* A {@link UpdatableFontDir.FontFileParser} for testing. Instead of using real font files,
* this test uses fake font files. A fake font file has its PostScript naem and revision as the
@@ -1097,6 +1106,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1116,6 +1126,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1);
@@ -1135,6 +1146,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1154,6 +1166,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1173,6 +1186,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1192,6 +1206,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1211,6 +1226,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1230,6 +1246,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void signatureAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1249,6 +1266,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1268,6 +1286,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1);
@@ -1287,6 +1306,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1306,6 +1326,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf and bar.ttf
installTestFontFile(2 /* numFonts */, 1 /* version */);
@@ -1325,6 +1346,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1344,6 +1366,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1363,6 +1386,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1382,6 +1406,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1401,6 +1426,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1420,6 +1446,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1439,6 +1466,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1458,6 +1486,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1477,6 +1506,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1497,6 +1527,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater() {
// Install font families, foo.ttf, bar.ttf.
installTestFontFamilies(1 /* version */);
@@ -1517,6 +1548,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
@@ -1537,6 +1569,7 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FIX_FONT_UPDATE_FAILURE)
public void dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater() {
// Install font file, foo.ttf
installTestFontFile(1 /* numFonts */, 1 /* version */);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
index 43bf537..72caae3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
@@ -242,7 +242,7 @@
private void stopUser(int userId) throws RemoteException, InterruptedException {
runWithLatch("stop user", countDownLatch -> {
ActivityManager.getService()
- .stopUser(userId, /* force= */ true, new IStopUserCallback.Stub() {
+ .stopUserWithCallback(userId, new IStopUserCallback.Stub() {
@Override
public void userStopped(int userId) {
countDownLatch.countDown();
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 52df010..eac9929 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -85,7 +85,6 @@
import android.os.test.TestLooper;
import android.service.dreams.DreamManagerInternal;
import android.telecom.TelecomManager;
-import android.util.FeatureFlagUtils;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
@@ -743,15 +742,8 @@
void assertSwitchKeyboardLayout(int direction, int displayId) {
mTestLooper.dispatchAll();
- if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
- verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction),
- eq(displayId), eq(mImeTargetWindowToken));
- verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
- } else {
- verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction));
- verify(mInputMethodManagerInternal, never())
- .onSwitchKeyboardLayoutShortcut(anyInt(), anyInt(), any());
- }
+ verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction),
+ eq(displayId), eq(mImeTargetWindowToken));
}
void assertTakeBugreport() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 1355092..10eae57 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -46,6 +46,7 @@
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
+import static android.server.wm.ActivityManagerTestBase.isTablet;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -75,6 +76,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -122,6 +124,7 @@
import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
import com.android.server.wm.utils.MockTracker;
+import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.Before;
@@ -1295,6 +1298,12 @@
*/
@Test
public void testDeliverIntentToTopActivityOfNonTopDisplay() {
+ // TODO(b/330152508): Remove check once legacy multi-display behaviour can coexist with
+ // desktop windowing mode
+ // Ignore test if desktop windowing is enabled on tablets as legacy multi-display
+ // behaviour will not be respected
+ assumeFalse(Flags.enableDesktopWindowingMode() && isTablet());
+
final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index 5aa4ba3e..695faa5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -504,22 +504,37 @@
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isTrue();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).isEqualTo(
- "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
- + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
- + "false; callingUidProcState: NONEXISTENT; "
- + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
- + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
- + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
- + ".ALLOW_BAL; balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
- + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
- + "isCallForResult: false; isPendingIntent: false; autoOptInReason: "
- + "notPendingIntent; realCallingPackage: uid=1[debugOnly]; "
- + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
- + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
- + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
- + "originatingPendingIntent: null; realCallerApp: null; "
- + "balAllowedByPiSender: BSP.ALLOW_BAL; resultIfPiSenderAllowsBal: null]");
+ assertThat(balState.toString()).contains(
+ "[callingPackage: package.app1; "
+ + "callingPackageTargetSdk: -1; "
+ + "callingUid: 10001; "
+ + "callingPid: 11001; "
+ + "appSwitchState: 0; "
+ + "callingUidHasAnyVisibleWindow: false; "
+ + "callingUidProcState: NONEXISTENT; "
+ + "isCallingUidPersistentSystemProcess: false; "
+ + "forcedBalByPiSender: BSP.NONE; "
+ + "intent: Intent { cmp=package.app3/someClass }; "
+ + "callerApp: mCallerApp; "
+ + "inVisibleTask: false; "
+ + "balAllowedByPiCreator: BSP.ALLOW_BAL; "
+ + "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
+ + "resultIfPiCreatorAllowsBal: null; "
+ + "hasRealCaller: true; "
+ + "isCallForResult: false; "
+ + "isPendingIntent: false; "
+ + "autoOptInReason: notPendingIntent; "
+ + "realCallingPackage: uid=1[debugOnly]; "
+ + "realCallingPackageTargetSdk: -1; "
+ + "realCallingUid: 1; "
+ + "realCallingPid: 1; "
+ + "realCallingUidHasAnyVisibleWindow: false; "
+ + "realCallingUidProcState: NONEXISTENT; "
+ + "isRealCallingUidPersistentSystemProcess: false; "
+ + "originatingPendingIntent: null; "
+ + "realCallerApp: null; "
+ + "balAllowedByPiSender: BSP.ALLOW_BAL; "
+ + "resultIfPiSenderAllowsBal: null");
}
@Test
@@ -588,21 +603,36 @@
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isFalse();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).isEqualTo(
- "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
- + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
- + "false; callingUidProcState: NONEXISTENT; "
- + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
- + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
- + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
- + ".NONE; balAllowedByPiCreatorWithHardening: BSP.NONE; "
- + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
- + "isCallForResult: false; isPendingIntent: true; autoOptInReason: "
- + "null; realCallingPackage: uid=1[debugOnly]; "
- + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
- + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
- + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
- + "originatingPendingIntent: PendingIntentRecord; realCallerApp: null; "
- + "balAllowedByPiSender: BSP.ALLOW_FGS; resultIfPiSenderAllowsBal: null]");
+ assertThat(balState.toString()).contains(
+ "[callingPackage: package.app1; "
+ + "callingPackageTargetSdk: -1; "
+ + "callingUid: 10001; "
+ + "callingPid: 11001; "
+ + "appSwitchState: 0; "
+ + "callingUidHasAnyVisibleWindow: false; "
+ + "callingUidProcState: NONEXISTENT; "
+ + "isCallingUidPersistentSystemProcess: false; "
+ + "forcedBalByPiSender: BSP.NONE; "
+ + "intent: Intent { cmp=package.app3/someClass }; "
+ + "callerApp: mCallerApp; "
+ + "inVisibleTask: false; "
+ + "balAllowedByPiCreator: BSP.NONE; "
+ + "balAllowedByPiCreatorWithHardening: BSP.NONE; "
+ + "resultIfPiCreatorAllowsBal: null; "
+ + "hasRealCaller: true; "
+ + "isCallForResult: false; "
+ + "isPendingIntent: true; "
+ + "autoOptInReason: null; "
+ + "realCallingPackage: uid=1[debugOnly]; "
+ + "realCallingPackageTargetSdk: -1; "
+ + "realCallingUid: 1; "
+ + "realCallingPid: 1; "
+ + "realCallingUidHasAnyVisibleWindow: false; "
+ + "realCallingUidProcState: NONEXISTENT; "
+ + "isRealCallingUidPersistentSystemProcess: false; "
+ + "originatingPendingIntent: PendingIntentRecord; "
+ + "realCallerApp: null; "
+ + "balAllowedByPiSender: BSP.ALLOW_FGS; "
+ + "resultIfPiSenderAllowsBal: null");
}
}
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index e8ffe54..e00627e 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -44,6 +44,7 @@
aconfig_declarations {
name: "usb_flags",
package: "com.android.server.usb.flags",
+ container: "system",
srcs: ["**/usb_flags.aconfig"],
}
diff --git a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
index ea6e502..a7c5ddb 100644
--- a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
+++ b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
@@ -1,4 +1,5 @@
package: "com.android.server.usb.flags"
+container: "system"
flag {
name: "allow_restriction_of_overlay_activities"
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 10c17c1..7db4180 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9898,6 +9898,50 @@
*/
public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING =
"satellite_information_redirect_url_string";
+ /**
+ * Indicate whether a carrier supports emergency messaging. When this config is {@code false},
+ * emergency call to satellite T911 handover will be disabled.
+ *
+ * This will need agreement with carriers before enabling this flag.
+ *
+ * The default value is false.
+ *
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL =
+ "emergency_messaging_supported_bool";
+
+ /**
+ * An integer key holds the timeout duration in milliseconds used to determine whether to hand
+ * over an emergency call to satellite T911.
+ *
+ * The timer is started when there is an ongoing emergency call, and the IMS is not registered,
+ * and cellular service is not available. When the timer expires,
+ * {@link com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender} will send the
+ * event {@link TelephonyManager#EVENT_DISPLAY_EMERGENCY_MESSAGE} to Dialer, which will then
+ * prompt user to switch to using satellite emergency messaging.
+ *
+ * The default value is 30 seconds.
+ *
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT =
+ "emergency_call_to_satellite_t911_handover_timeout_millis_int";
+
+ /**
+ * An int array that contains default capabilities for carrier enabled satellite roaming.
+ * If any PLMN is provided from the entitlement server, and it is not listed in
+ * {@link #KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE}, default capabilities
+ * will be used instead.
+ * <p>
+ * The default capabilities are
+ * {@link NetworkRegistrationInfo#SERVICE_TYPE_SMS}, and
+ * {@link NetworkRegistrationInfo#SERVICE_TYPE_MMS}
+ *
+ * @hide
+ */
+ public static final String KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY =
+ "carrier_roaming_satellite_default_services_int_array";
/**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
@@ -11045,7 +11089,15 @@
sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
sDefaults.putString(KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING, "androidSatmode");
sDefaults.putString(KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING, "");
+ sDefaults.putIntArray(KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY,
+ new int[] {
+ NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+ NetworkRegistrationInfo.SERVICE_TYPE_MMS
+ });
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
+ sDefaults.putBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, false);
+ sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
+ (int) TimeUnit.SECONDS.toMillis(30));
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index c5f2d42..ba7ba532 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3137,7 +3137,7 @@
if (useRootLocale) {
configurationKey.setLocale(Locale.ROOT);
}
- cacheKey = Pair.create(context.getPackageName(), configurationKey);
+ cacheKey = Pair.create(context.getPackageName() + ", subid=" + subId, configurationKey);
synchronized (sResourcesCache) {
Resources cached = sResourcesCache.get(cacheKey);
if (cached != null) {
diff --git a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index 06fc3c6..579fda3 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -26,11 +26,13 @@
/**
* Called when satellite datagram send state changed.
*
+ * @param datagramType The datagram type of currently being sent.
* @param state The new send datagram transfer state.
* @param sendPendingCount The number of datagrams that are currently being sent.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
- void onSendDatagramStateChanged(in int state, in int sendPendingCount, in int errorCode);
+ void onSendDatagramStateChanged(int datagramType, int state, int sendPendingCount,
+ int errorCode);
/**
* Called when satellite datagram receive state changed.
@@ -39,7 +41,7 @@
* @param receivePendingCount The number of datagrams that are currently pending to be received.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
- void onReceiveDatagramStateChanged(in int state, in int receivePendingCount, in int errorCode);
+ void onReceiveDatagramStateChanged(int state, int receivePendingCount, int errorCode);
/**
* Called when the satellite position changed.
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 20b24b9..87bb0f0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -992,12 +992,19 @@
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2;
+ /**
+ * This type of datagram is used to keep the device in satellite connected state or check if
+ * there is any incoming message.
+ * @hide
+ */
+ public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3;
/** @hide */
@IntDef(prefix = "DATAGRAM_TYPE_", value = {
DATAGRAM_TYPE_UNKNOWN,
DATAGRAM_TYPE_SOS_MESSAGE,
- DATAGRAM_TYPE_LOCATION_SHARING
+ DATAGRAM_TYPE_LOCATION_SHARING,
+ DATAGRAM_TYPE_KEEP_ALIVE
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatagramType {}
@@ -1077,8 +1084,13 @@
}
@Override
- public void onSendDatagramStateChanged(int state, int sendPendingCount,
- int errorCode) {
+ public void onSendDatagramStateChanged(int datagramType, int state,
+ int sendPendingCount, int errorCode) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSendDatagramStateChanged(datagramType,
+ state, sendPendingCount, errorCode)));
+
+ // For backward compatibility
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> callback.onSendDatagramStateChanged(
state, sendPendingCount, errorCode)));
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index e020970..d8bd662 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -52,6 +52,19 @@
@SatelliteManager.SatelliteResult int errorCode);
/**
+ * Called when satellite datagram send state changed.
+ *
+ * @param datagramType The datagram type of currently being sent.
+ * @param state The new send datagram transfer state.
+ * @param sendPendingCount The number of datagrams that are currently being sent.
+ * @param errorCode If datagram transfer failed, the reason for failure.
+ *
+ * @hide
+ */
+ void onSendDatagramStateChanged(@SatelliteManager.DatagramType int datagramType,
+ @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
+ @SatelliteManager.SatelliteResult int errorCode);
+ /**
* Called when satellite datagram receive state changed.
*
* @param state The new receive datagram transfer state.
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
index 511c948..1390218 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/rotation/RotationTransition.kt
@@ -17,8 +17,12 @@
package com.android.server.wm.flicker.activityembedding.rotation
import android.platform.test.annotations.Presubmit
+import android.tools.Position
+import android.tools.datatypes.Rect
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.Condition
+import android.tools.traces.DeviceStateDump
import android.tools.traces.component.ComponentNameMatcher
import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.helpers.setRotation
@@ -30,7 +34,14 @@
override val transition: FlickerBuilder.() -> Unit = {
setup { this.setRotation(flicker.scenario.startRotation) }
teardown { testApp.exit(wmHelper) }
- transitions { this.setRotation(flicker.scenario.endRotation) }
+ transitions {
+ this.setRotation(flicker.scenario.endRotation)
+ if (!flicker.scenario.isTablet) {
+ wmHelper.StateSyncBuilder()
+ .add(navBarInPosition(flicker.scenario.isGesturalNavigation))
+ .waitForAndVerify()
+ }
+ }
}
/** {@inheritDoc} */
@@ -76,4 +87,37 @@
appLayerRotates_StartingPos()
appLayerRotates_EndingPos()
}
+
+ private fun navBarInPosition(isGesturalNavigation: Boolean): Condition<DeviceStateDump> {
+ return Condition("navBarPosition") { dump ->
+ val display =
+ dump.layerState.displays.filterNot { it.isOff }.minByOrNull { it.id }
+ ?: error("There is no display!")
+ val displayArea = display.layerStackSpace
+ val navBarPosition = display.navBarPosition(isGesturalNavigation)
+ val navBarRegion = dump.layerState
+ .getLayerWithBuffer(ComponentNameMatcher.NAV_BAR)
+ ?.visibleRegion?.bounds ?: Rect.EMPTY
+
+ when (navBarPosition) {
+ Position.TOP ->
+ navBarRegion.top == displayArea.top &&
+ navBarRegion.left == displayArea.left &&
+ navBarRegion.right == displayArea.right
+ Position.BOTTOM ->
+ navBarRegion.bottom == displayArea.bottom &&
+ navBarRegion.left == displayArea.left &&
+ navBarRegion.right == displayArea.right
+ Position.LEFT ->
+ navBarRegion.left == displayArea.left &&
+ navBarRegion.top == displayArea.top &&
+ navBarRegion.bottom == displayArea.bottom
+ Position.RIGHT ->
+ navBarRegion.right == displayArea.right &&
+ navBarRegion.top == displayArea.top &&
+ navBarRegion.bottom == displayArea.bottom
+ else -> error("Unknown position $navBarPosition")
+ }
+ }
+ }
}
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
index 8e210d4..f1df8a6 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -123,7 +123,9 @@
@Test
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
- @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
+ @FlakyTest(bugId = 227143265)
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
@FlakyTest(bugId = 278227468)
@Test
diff --git a/tests/FlickerTests/README.md b/tests/FlickerTests/README.md
index 6b28fdf..7429250 100644
--- a/tests/FlickerTests/README.md
+++ b/tests/FlickerTests/README.md
@@ -7,82 +7,17 @@
## Adding a Test
-By default tests should inherit from `RotationTestBase` or `NonRotationTestBase` and must override the variable `transitionToRun` (Kotlin) or the function `getTransitionToRun()` (Java).
-Only tests that are not supported by these classes should inherit directly from the `FlickerTestBase` class.
+By default, tests should inherit from `TestBase` and override the variable `transition` (Kotlin) or the function `getTransition()` (Java).
-### Rotation animations and transitions
+Inheriting from this class ensures the common assertions will be executed, namely:
-Tests that rotate the device should inherit from `RotationTestBase`.
-Tests that inherit from the class automatically receive start and end rotation values.
-Moreover, these tests inherit the following checks:
* all regions on the screen are covered
* status bar is always visible
-* status bar rotates
+* status bar is at the correct position at the start and end of the transition
* nav bar is always visible
-* nav bar is rotates
+* nav bar is at the correct position at the start and end of the transition
The default tests can be disabled by overriding the respective methods and including an `@Ignore` annotation.
-### Non-Rotation animations and transitions
+For more examples of how a test looks like check `ChangeAppRotationTest` within the `Rotation` subdirectory.
-`NonRotationTestBase` was created to make it easier to write tests that do not involve rotation (e.g., `Pip`, `split screen` or `IME`).
-Tests that inherit from the class are automatically executed twice: once in portrait and once in landscape mode and the assertions are checked independently.
-Moreover, these tests inherit the following checks:
-* all regions on the screen are covered
-* status bar is always visible
-* nav bar is always visible
-
-The default tests can be disabled by overriding the respective methods and including an `@Ignore` annotation.
-
-### Exceptional cases
-
-Tests that rotate the device should inherit from `RotationTestBase`.
-This class allows the test to be freely configured and does not provide any assertions.
-
-
-### Example
-
-Start by defining common or error prone transitions using `TransitionRunner`.
-```kotlin
-@LargeTest
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MyTest(
- beginRotationName: String,
- beginRotation: Int
-) : NonRotationTestBase(beginRotationName, beginRotation) {
- init {
- mTestApp = MyAppHelper(InstrumentationRegistry.getInstrumentation())
- }
-
- override val transitionToRun: TransitionRunner
- get() = TransitionRunner.newBuilder()
- .withTag("myTest")
- .recordAllRuns()
- .runBefore { device.pressHome() }
- .runBefore { device.waitForIdle() }
- .run { testApp.open() }
- .runAfter{ testApp.exit() }
- .repeat(2)
- .includeJankyRuns()
- .build()
-
- @Test
- fun myWMTest() {
- checkResults {
- WmTraceSubject.assertThat(it)
- .showsAppWindow(MyTestApp)
- .forAllEntries()
- }
- }
-
- @Test
- fun mySFTest() {
- checkResults {
- LayersTraceSubject.assertThat(it)
- .showsLayer(MyTestApp)
- .forAllEntries()
- }
- }
-}
-```
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index e60764f..80282c3 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -33,7 +33,6 @@
import android.os.Bundle
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
-import android.provider.Settings
import android.util.proto.ProtoOutputStream
import android.view.InputDevice
import android.view.inputmethod.InputMethodInfo
@@ -47,9 +46,7 @@
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
-import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
-import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -234,631 +231,326 @@
}
@Test
- fun testDefaultUi_getKeyboardLayouts() {
- NewSettingsApiFlag(false).use {
- val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
- assertNotEquals(
- "Default UI: Keyboard layout API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "Default UI: Keyboard layout API should provide English(US) layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- }
+ fun testGetKeyboardLayouts() {
+ val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+ assertNotEquals(
+ "Keyboard layout API should not return empty array",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue(
+ "Keyboard layout API should provide English(US) layout",
+ hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
}
@Test
- fun testNewUi_getKeyboardLayouts() {
- NewSettingsApiFlag(true).use {
- val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
- assertNotEquals(
- "New UI: Keyboard layout API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "New UI: Keyboard layout API should provide English(US) layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- }
+ fun testGetKeyboardLayout() {
+ val keyboardLayout =
+ keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
+ assertEquals("getKeyboardLayout API should return correct Layout from " +
+ "available layouts",
+ ENGLISH_US_LAYOUT_DESCRIPTOR,
+ keyboardLayout!!.descriptor
+ )
}
@Test
- fun testDefaultUi_getKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(false).use {
- val keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier)
- assertNotEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "Default UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
- "layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
+ fun testGetSetKeyboardLayoutForInputDevice_withImeInfo() {
+ val imeSubtype = createImeSubtype()
- val vendorSpecificKeyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutsForInputDevice(
- vendorSpecificKeyboardDevice.identifier
- )
- assertEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should return only vendor " +
- "specific layout",
+ keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+ ENGLISH_UK_LAYOUT_DESCRIPTOR
+ )
+ var result =
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+ )
+ assertEquals(
+ "getKeyboardLayoutForInputDevice API should return the set layout",
+ ENGLISH_UK_LAYOUT_DESCRIPTOR,
+ result.layoutDescriptor
+ )
+
+ // This should replace previously set layout
+ keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ result =
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
+ )
+ assertEquals(
+ "getKeyboardLayoutForInputDevice API should return the last set layout",
+ ENGLISH_US_LAYOUT_DESCRIPTOR,
+ result.layoutDescriptor
+ )
+ }
+
+ @Test
+ fun testGetKeyboardLayoutListForInputDevice() {
+ // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
+ var keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("hi-Latn")
+ )
+ assertNotEquals(
+ "getKeyboardLayoutListForInputDevice API should return the list of " +
+ "supported layouts with matching script code",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(US) layout for hi-Latn",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(No script code) layout for hi-Latn",
+ containsLayout(
+ keyboardLayouts,
+ createLayoutDescriptor("keyboard_layout_english_without_script_code")
+ )
+ )
+
+ // Check Layouts for "hi" which by default uses 'Deva' script.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("hi")
+ )
+ assertEquals("getKeyboardLayoutListForInputDevice API should return empty " +
+ "list if no supported layouts available",
+ 0,
+ keyboardLayouts.size
+ )
+
+ // If user manually selected some layout, always provide it in the layout list
+ val imeSubtype = createImeSubtypeForLanguageTag("hi")
+ keyboardLayoutManager.setKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ imeSubtype
+ )
+ assertEquals("getKeyboardLayoutListForInputDevice API should return user " +
+ "selected layout even if the script is incompatible with IME",
1,
- vendorSpecificKeyboardLayouts.size
- )
- assertEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should return vendor specific " +
- "layout",
- VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR,
- vendorSpecificKeyboardLayouts[0].descriptor
- )
- }
- }
+ keyboardLayouts.size
+ )
- @Test
- fun testNewUi_getKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(true).use {
- val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
- assertNotEquals(
- "New UI: getKeyboardLayoutsForInputDevice API should not return empty array",
- 0,
- keyboardLayouts.size
- )
- assertTrue(
- "New UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
- "layout",
- hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- }
- }
-
- @Test
- fun testDefaultUi_getSetCurrentKeyboardLayoutForInputDevice() {
- NewSettingsApiFlag(false).use {
- assertNull(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null if " +
- "nothing was set",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
-
- keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier,
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- val keyboardLayout =
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- assertEquals(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return the set " +
- "layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayout
- )
- }
- }
-
- @Test
- fun testNewUi_getSetCurrentKeyboardLayoutForInputDevice() {
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.setCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier,
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertNull(
- "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null " +
- "even after setCurrentKeyboardLayoutForInputDevice",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testDefaultUi_getEnabledKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(false).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
-
- val keyboardLayouts =
- keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
- keyboardDevice.identifier
- )
- assertEquals(
- "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return added " +
- "layout",
- 1,
- keyboardLayouts.size
- )
- assertEquals(
- "Default UI: getEnabledKeyboardLayoutsForInputDevice API should return " +
- "English(US) layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayouts[0]
- )
- assertEquals(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
- "English(US) layout (Auto select the first enabled layout)",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
-
- keyboardLayoutManager.removeKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertEquals(
- "Default UI: getKeyboardLayoutsForInputDevice API should return 0 layouts",
- 0,
- keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
- keyboardDevice.identifier
- ).size
- )
- assertNull(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return null after " +
- "the enabled layout is removed",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testNewUi_getEnabledKeyboardLayoutsForInputDevice() {
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
-
- assertEquals(
- "New UI: getEnabledKeyboardLayoutsForInputDevice API should return always return " +
- "an empty array",
- 0,
- keyboardLayoutManager.getEnabledKeyboardLayoutsForInputDevice(
- keyboardDevice.identifier
- ).size
- )
- assertNull(
- "New UI: getCurrentKeyboardLayoutForInputDevice API should always return null",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testDefaultUi_switchKeyboardLayout() {
- NewSettingsApiFlag(false).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertEquals(
- "Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
- "English(US) layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
-
- keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
-
- // Throws null pointer because trying to show toast using TestLooper
- assertThrows(NullPointerException::class.java) { testLooper.dispatchAll() }
- assertEquals("Default UI: getCurrentKeyboardLayoutForInputDevice API should return " +
- "English(UK) layout",
- ENGLISH_UK_LAYOUT_DESCRIPTOR,
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testNewUi_switchKeyboardLayout() {
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- keyboardLayoutManager.addKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
-
- keyboardLayoutManager.switchKeyboardLayout(DEVICE_ID, 1)
- testLooper.dispatchAll()
-
- assertNull("New UI: getCurrentKeyboardLayoutForInputDevice API should always return " +
- "null",
- keyboardLayoutManager.getCurrentKeyboardLayoutForInputDevice(
- keyboardDevice.identifier
- )
- )
- }
- }
-
- @Test
- fun testDefaultUi_getKeyboardLayout() {
- NewSettingsApiFlag(false).use {
- val keyboardLayout =
- keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
- assertEquals("Default UI: getKeyboardLayout API should return correct Layout from " +
- "available layouts",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayout!!.descriptor
- )
- }
- }
-
- @Test
- fun testNewUi_getKeyboardLayout() {
- NewSettingsApiFlag(true).use {
- val keyboardLayout =
- keyboardLayoutManager.getKeyboardLayout(ENGLISH_US_LAYOUT_DESCRIPTOR)
- assertEquals("New UI: getKeyboardLayout API should return correct Layout from " +
- "available layouts",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- keyboardLayout!!.descriptor
- )
- }
- }
-
- @Test
- fun testDefaultUi_getSetKeyboardLayoutForInputDevice_WithImeInfo() {
- NewSettingsApiFlag(false).use {
- val imeSubtype = createImeSubtype()
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertEquals(
- "Default UI: getKeyboardLayoutForInputDevice API should always return " +
- "KeyboardLayoutSelectionResult.FAILED",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
- )
- )
- }
- }
-
- @Test
- fun testNewUi_getSetKeyboardLayoutForInputDevice_withImeInfo() {
- NewSettingsApiFlag(true).use {
- val imeSubtype = createImeSubtype()
-
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- var result =
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
- )
- assertEquals(
- "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
- ENGLISH_UK_LAYOUT_DESCRIPTOR,
- result.layoutDescriptor
- )
-
- // This should replace previously set layout
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- result =
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
- )
- assertEquals(
- "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
- result.layoutDescriptor
- )
- }
- }
-
- @Test
- fun testDefaultUi_getKeyboardLayoutListForInputDevice() {
- NewSettingsApiFlag(false).use {
- val keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ // Special case Japanese: UScript ignores provided script code for certain language tags
+ // Should manually match provided script codes and then rely on Uscript to derive
+ // script from language tags and match those.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtype()
- )
- assertEquals("Default UI: getKeyboardLayoutListForInputDevice API should always " +
- "return empty array",
- 0,
- keyboardLayouts.size
+ createImeSubtypeForLanguageTag("ja-Latn-JP")
)
- }
- }
+ assertNotEquals(
+ "getKeyboardLayoutListForInputDevice API should return the list of " +
+ "supported layouts with matching script code for ja-Latn-JP",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(US) layout for ja-Latn-JP",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(No script code) layout for ja-Latn-JP",
+ containsLayout(
+ keyboardLayouts,
+ createLayoutDescriptor("keyboard_layout_english_without_script_code")
+ )
+ )
- @Test
- fun testNewUi_getKeyboardLayoutListForInputDevice() {
- NewSettingsApiFlag(true).use {
- // Check Layouts for "hi-Latn". It should return all 'Latn' keyboard layouts
- var keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ // If script code not explicitly provided for Japanese should rely on Uscript to find
+ // derived script code and hence no suitable layout will be found.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("hi-Latn")
- )
- assertNotEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
- "supported layouts with matching script code",
- 0,
- keyboardLayouts.size
+ createImeSubtypeForLanguageTag("ja-JP")
)
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(US) layout for hi-Latn",
- containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(No script code) layout for hi-Latn",
- containsLayout(
- keyboardLayouts,
- createLayoutDescriptor("keyboard_layout_english_without_script_code")
- )
- )
+ assertEquals(
+ "getKeyboardLayoutListForInputDevice API should return empty list of " +
+ "supported layouts with matching script code for ja-JP",
+ 0,
+ keyboardLayouts.size
+ )
- // Check Layouts for "hi" which by default uses 'Deva' script.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("hi")
- )
- assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return empty " +
- "list if no supported layouts available",
- 0,
- keyboardLayouts.size
+ // If IME doesn't have a corresponding language tag, then should show all available
+ // layouts no matter the script code.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo, null
)
-
- // If user manually selected some layout, always provide it in the layout list
- val imeSubtype = createImeSubtypeForLanguageTag("hi")
- keyboardLayoutManager.setKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- imeSubtype
- )
- assertEquals("New UI: getKeyboardLayoutListForInputDevice API should return user " +
- "selected layout even if the script is incompatible with IME",
- 1,
- keyboardLayouts.size
- )
-
- // Special case Japanese: UScript ignores provided script code for certain language tags
- // Should manually match provided script codes and then rely on Uscript to derive
- // script from language tags and match those.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("ja-Latn-JP")
- )
- assertNotEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
- "supported layouts with matching script code for ja-Latn-JP",
- 0,
- keyboardLayouts.size
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(US) layout for ja-Latn-JP",
- containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
- "containing English(No script code) layout for ja-Latn-JP",
- containsLayout(
- keyboardLayouts,
- createLayoutDescriptor("keyboard_layout_english_without_script_code")
- )
- )
-
- // If script code not explicitly provided for Japanese should rely on Uscript to find
- // derived script code and hence no suitable layout will be found.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("ja-JP")
- )
- assertEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return empty list of " +
- "supported layouts with matching script code for ja-JP",
- 0,
- keyboardLayouts.size
- )
-
- // If IME doesn't have a corresponding language tag, then should show all available
- // layouts no matter the script code.
- keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo, null
- )
- assertNotEquals(
- "New UI: getKeyboardLayoutListForInputDevice API should return all layouts if" +
- "language tag or subtype not provided",
- 0,
- keyboardLayouts.size
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Latin " +
- "layouts if language tag or subtype not provided",
- containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
- )
- assertTrue("New UI: getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
- "layouts if language tag or subtype not provided",
- containsLayout(
- keyboardLayouts,
- createLayoutDescriptor("keyboard_layout_russian")
- )
- )
- }
- }
-
- @Test
- fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
- NewSettingsApiFlag(true).use {
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("en-US"),
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("en-GB"),
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("de"),
- GERMAN_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("fr-FR"),
- createLayoutDescriptor("keyboard_layout_french")
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTag("ru"),
+ assertNotEquals(
+ "getKeyboardLayoutListForInputDevice API should return all layouts if" +
+ "language tag or subtype not provided",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should contain Latin " +
+ "layouts if language tag or subtype not provided",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("getKeyboardLayoutListForInputDevice API should contain Cyrillic " +
+ "layouts if language tag or subtype not provided",
+ containsLayout(
+ keyboardLayouts,
createLayoutDescriptor("keyboard_layout_russian")
)
- assertEquals(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
- "KeyboardLayoutSelectionResult.FAILED when no layout available",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("it")
- )
- )
- assertEquals(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
- "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
- "available",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTag("en-Deva")
- )
- )
- }
+ )
}
@Test
- fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
- NewSettingsApiFlag(true).use {
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
- ENGLISH_US_LAYOUT_DESCRIPTOR
+ fun testGetDefaultKeyboardLayoutForInputDevice_withImeLanguageTag() {
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("en-US"),
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("en-GB"),
+ ENGLISH_UK_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("de"),
+ GERMAN_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("fr-FR"),
+ createLayoutDescriptor("keyboard_layout_french")
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTag("ru"),
+ createLayoutDescriptor("keyboard_layout_russian")
+ )
+ assertEquals(
+ "getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout available",
+ KeyboardLayoutSelectionResult.FAILED,
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("it")
)
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
- createLayoutDescriptor("keyboard_layout_english_us_dvorak")
- )
- // Try to match layout type even if country doesn't match
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
- createLayoutDescriptor("keyboard_layout_english_us_dvorak")
- )
- // Choose layout based on layout type priority, if layout type is not provided by IME
- // (Qwerty > Dvorak > Extended)
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
- ENGLISH_US_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
- ENGLISH_UK_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
- GERMAN_LAYOUT_DESCRIPTOR
- )
- // Wrong layout type should match with language if provided layout type not available
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
- GERMAN_LAYOUT_DESCRIPTOR
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
- createLayoutDescriptor("keyboard_layout_french")
- )
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
- createLayoutDescriptor("keyboard_layout_russian_qwerty")
- )
- // If layout type is empty then prioritize KCM with empty layout type
- assertCorrectLayout(
- keyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
- createLayoutDescriptor("keyboard_layout_russian")
- )
- assertEquals("New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+ )
+ assertEquals(
+ "getDefaultKeyboardLayoutForInputDevice should return " +
"KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
"available",
- KeyboardLayoutSelectionResult.FAILED,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- keyboardDevice.identifier, USER_ID, imeInfo,
- createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
- )
+ KeyboardLayoutSelectionResult.FAILED,
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("en-Deva")
)
- }
+ )
}
@Test
- fun testNewUi_getDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
- NewSettingsApiFlag(true).use {
- val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
- // Should return English dvorak even if IME current layout is French, since HW says the
- // keyboard is a Dvorak keyboard
- assertCorrectLayout(
- englishDvorakKeyboardDevice,
- frenchSubtype,
- createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ fun testGetDefaultKeyboardLayoutForInputDevice_withImeLanguageTagAndLayoutType() {
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-US", "qwerty"),
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-US", "dvorak"),
+ createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ )
+ // Try to match layout type even if country doesn't match
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-GB", "dvorak"),
+ createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ )
+ // Choose layout based on layout type priority, if layout type is not provided by IME
+ // (Qwerty > Dvorak > Extended)
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-US", ""),
+ ENGLISH_US_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("en-GB", "qwerty"),
+ ENGLISH_UK_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"),
+ GERMAN_LAYOUT_DESCRIPTOR
+ )
+ // Wrong layout type should match with language if provided layout type not available
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"),
+ GERMAN_LAYOUT_DESCRIPTOR
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("fr-FR", "azerty"),
+ createLayoutDescriptor("keyboard_layout_french")
+ )
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("ru", "qwerty"),
+ createLayoutDescriptor("keyboard_layout_russian_qwerty")
+ )
+ // If layout type is empty then prioritize KCM with empty layout type
+ assertCorrectLayout(
+ keyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+ createLayoutDescriptor("keyboard_layout_russian")
+ )
+ assertEquals("getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+ "available",
+ KeyboardLayoutSelectionResult.FAILED,
+ keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
)
+ )
+ }
- // Back to back changing HW keyboards with same product and vendor ID but different
- // language and layout type should configure the layouts correctly.
- assertCorrectLayout(
- englishQwertyKeyboardDevice,
- frenchSubtype,
- createLayoutDescriptor("keyboard_layout_english_us")
- )
+ @Test
+ fun testGetDefaultKeyboardLayoutForInputDevice_withHwLanguageTagAndLayoutType() {
+ val frenchSubtype = createImeSubtypeForLanguageTagAndLayoutType("fr", "azerty")
+ // Should return English dvorak even if IME current layout is French, since HW says the
+ // keyboard is a Dvorak keyboard
+ assertCorrectLayout(
+ englishDvorakKeyboardDevice,
+ frenchSubtype,
+ createLayoutDescriptor("keyboard_layout_english_us_dvorak")
+ )
- // Fallback to IME information if the HW provided layout script is incompatible with the
- // provided IME subtype
- assertCorrectLayout(
- englishDvorakKeyboardDevice,
- createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
- createLayoutDescriptor("keyboard_layout_russian")
- )
- }
+ // Back to back changing HW keyboards with same product and vendor ID but different
+ // language and layout type should configure the layouts correctly.
+ assertCorrectLayout(
+ englishQwertyKeyboardDevice,
+ frenchSubtype,
+ createLayoutDescriptor("keyboard_layout_english_us")
+ )
+
+ // Fallback to IME information if the HW provided layout script is incompatible with the
+ // provided IME subtype
+ assertCorrectLayout(
+ englishDvorakKeyboardDevice,
+ createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
+ createLayoutDescriptor("keyboard_layout_russian")
+ )
}
@Test
@@ -867,27 +559,25 @@
KeyboardLayoutManager.ImeInfo(0, imeInfo,
createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.eq(keyboardDevice.vendorId),
- ArgumentMatchers.eq(keyboardDevice.productId),
- ArgumentMatchers.eq(
- createByteArray(
- KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT,
- GERMAN_LAYOUT_NAME,
- KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
- "de-Latn",
- LAYOUT_TYPE_QWERTZ
- ),
+ keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(keyboardDevice.vendorId),
+ ArgumentMatchers.eq(keyboardDevice.productId),
+ ArgumentMatchers.eq(
+ createByteArray(
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT,
+ GERMAN_LAYOUT_NAME,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ "de-Latn",
+ LAYOUT_TYPE_QWERTZ
),
- ArgumentMatchers.eq(keyboardDevice.deviceBus),
- )
- }
+ ),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
+ )
}
}
@@ -897,27 +587,25 @@
KeyboardLayoutManager.ImeInfo(0, imeInfo,
createImeSubtypeForLanguageTagAndLayoutType("de-Latn", "qwertz")))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
- ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
- ArgumentMatchers.eq(
- createByteArray(
- "en",
- LAYOUT_TYPE_QWERTY,
- ENGLISH_US_LAYOUT_NAME,
- KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
- "de-Latn",
- LAYOUT_TYPE_QWERTZ
- )
- ),
- ArgumentMatchers.eq(keyboardDevice.deviceBus),
- )
- }
+ keyboardLayoutManager.onInputDeviceAdded(englishQwertyKeyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
+ ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
+ ArgumentMatchers.eq(
+ createByteArray(
+ "en",
+ LAYOUT_TYPE_QWERTY,
+ ENGLISH_US_LAYOUT_NAME,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ "de-Latn",
+ LAYOUT_TYPE_QWERTZ
+ )
+ ),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
+ )
}
}
@@ -925,27 +613,25 @@
fun testConfigurationLogged_onInputDeviceAdded_DefaultSelection() {
val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
- ExtendedMockito.verify {
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.eq(keyboardDevice.vendorId),
- ArgumentMatchers.eq(keyboardDevice.productId),
- ArgumentMatchers.eq(
- createByteArray(
- KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT,
- "Default",
- KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
- KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT
- ),
+ keyboardLayoutManager.onInputDeviceAdded(keyboardDevice.id)
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.eq(keyboardDevice.vendorId),
+ ArgumentMatchers.eq(keyboardDevice.productId),
+ ArgumentMatchers.eq(
+ createByteArray(
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT,
+ "Default",
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
+ KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+ LAYOUT_TYPE_DEFAULT
),
- ArgumentMatchers.eq(keyboardDevice.deviceBus),
- )
- }
+ ),
+ ArgumentMatchers.eq(keyboardDevice.deviceBus),
+ )
}
}
@@ -953,19 +639,17 @@
fun testConfigurationNotLogged_onInputDeviceChanged() {
val imeInfos = listOf(KeyboardLayoutManager.ImeInfo(0, imeInfo, createImeSubtype()))
Mockito.doReturn(imeInfos).`when`(keyboardLayoutManager).imeInfoListForLayoutMapping
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
- ExtendedMockito.verify({
- FrameworkStatsLog.write(
- ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
- ArgumentMatchers.anyBoolean(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any(ByteArray::class.java),
- ArgumentMatchers.anyInt(),
- )
- }, Mockito.times(0))
- }
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify({
+ FrameworkStatsLog.write(
+ ArgumentMatchers.eq(FrameworkStatsLog.KEYBOARD_CONFIGURED),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(ByteArray::class.java),
+ ArgumentMatchers.anyInt(),
+ )
+ }, Mockito.times(0))
}
@Test
@@ -975,18 +659,16 @@
Mockito.doReturn(false).`when`(keyboardLayoutManager).isVirtualDevice(
ArgumentMatchers.eq(keyboardDevice.id)
)
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
- ExtendedMockito.verify(
- notificationManager,
- Mockito.times(1)
- ).notifyAsUser(
- ArgumentMatchers.isNull(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any(),
- ArgumentMatchers.any()
- )
- }
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify(
+ notificationManager,
+ Mockito.times(1)
+ ).notifyAsUser(
+ ArgumentMatchers.isNull(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
}
@Test
@@ -996,18 +678,16 @@
Mockito.doReturn(true).`when`(keyboardLayoutManager).isVirtualDevice(
ArgumentMatchers.eq(keyboardDevice.id)
)
- NewSettingsApiFlag(true).use {
- keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
- ExtendedMockito.verify(
- notificationManager,
- Mockito.never()
- ).notifyAsUser(
- ArgumentMatchers.isNull(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any(),
- ArgumentMatchers.any()
- )
- }
+ keyboardLayoutManager.onInputDeviceChanged(keyboardDevice.id)
+ ExtendedMockito.verify(
+ notificationManager,
+ Mockito.never()
+ ).notifyAsUser(
+ ArgumentMatchers.isNull(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
}
private fun assertCorrectLayout(
@@ -1019,7 +699,7 @@
device.identifier, USER_ID, imeInfo, imeSubtype
)
assertEquals(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
+ "getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
expectedLayout,
result.layoutDescriptor
)
@@ -1123,21 +803,4 @@
info.serviceInfo.name = RECEIVER_NAME
return info
}
-
- private inner class NewSettingsApiFlag constructor(enabled: Boolean) : AutoCloseable {
- init {
- Settings.Global.putString(
- context.contentResolver,
- "settings_new_keyboard_ui", enabled.toString()
- )
- }
-
- override fun close() {
- Settings.Global.putString(
- context.contentResolver,
- "settings_new_keyboard_ui",
- ""
- )
- }
- }
}
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index d9a4c26..5cdfb28 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -90,7 +90,7 @@
//noinspection ResultOfMethodCallIgnored
mFile.delete();
mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
- 1024 * 1024, mReader, 1024, new TreeMap<>());
+ 1024 * 1024, mReader, 1024, new TreeMap<>(), () -> {});
}
@After
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
index a963890..001a09a 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java
@@ -67,7 +67,7 @@
@Test
public void allEnabledTraceMode() {
- final ProtoLogDataSource ds = new ProtoLogDataSource(() -> {}, () -> {}, () -> {});
+ final ProtoLogDataSource ds = new ProtoLogDataSource((c) -> {}, () -> {}, (c) -> {});
final ProtoLogDataSource.TlsState tlsState = createTlsState(
DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig(
@@ -154,7 +154,7 @@
private ProtoLogDataSource.TlsState createTlsState(
DataSourceConfigOuterClass.DataSourceConfig config) {
final ProtoLogDataSource ds =
- Mockito.spy(new ProtoLogDataSource(() -> {}, () -> {}, () -> {}));
+ Mockito.spy(new ProtoLogDataSource((c) -> {}, () -> {}, (c) -> {}));
ProtoInputStream configStream = new ProtoInputStream(config.toByteArray());
final ProtoLogDataSource.Instance dsInstance = Mockito.spy(
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 548adef..f6ac080 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -65,6 +65,7 @@
import java.util.List;
import java.util.Random;
import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicInteger;
import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
@@ -95,6 +96,7 @@
private PerfettoProtoLogImpl mProtoLog;
private Protolog.ProtoLogViewerConfig.Builder mViewerConfigBuilder;
private File mFile;
+ private Runnable mCacheUpdater;
private ProtoLogViewerConfigReader mReader;
@@ -152,9 +154,11 @@
Mockito.when(viewerConfigInputStreamProvider.getInputStream())
.thenAnswer(it -> new ProtoInputStream(mViewerConfigBuilder.build().toByteArray()));
+ mCacheUpdater = () -> {};
mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
- mProtoLog =
- new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader, new TreeMap<>());
+ mProtoLog = new PerfettoProtoLogImpl(
+ viewerConfigInputStreamProvider, mReader, new TreeMap<>(),
+ () -> mCacheUpdater.run());
}
@After
@@ -500,7 +504,8 @@
PerfettoTraceMonitor traceMonitor =
PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
- TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG, true)))
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
+ true)))
.build();
try {
traceMonitor.start();
@@ -526,6 +531,142 @@
Truth.assertThat(stacktrace).contains("stackTraceTrimmed");
}
+ @Test
+ public void cacheIsUpdatedWhenTracesStartAndStop() {
+ final AtomicInteger cacheUpdateCallCount = new AtomicInteger(0);
+ mCacheUpdater = cacheUpdateCallCount::incrementAndGet;
+
+ PerfettoTraceMonitor traceMonitor1 =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN,
+ false)))
+ .build();
+
+ PerfettoTraceMonitor traceMonitor2 =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
+ false)))
+ .build();
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(0);
+
+ try {
+ traceMonitor1.start();
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(1);
+
+ try {
+ traceMonitor2.start();
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(2);
+ } finally {
+ traceMonitor2.stop(mWriter);
+ }
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(3);
+
+ } finally {
+ traceMonitor1.stop(mWriter);
+ }
+
+ Truth.assertThat(cacheUpdateCallCount.get()).isEqualTo(4);
+ }
+
+ @Test
+ public void isEnabledUpdatesBasedOnRunningTraces() {
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isTrue();
+
+ PerfettoTraceMonitor traceMonitor1 =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.WARN,
+ false)))
+ .build();
+
+ PerfettoTraceMonitor traceMonitor2 =
+ PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
+ List.of(new PerfettoTraceMonitor.Builder.ProtoLogGroupOverride(
+ TestProtoLogGroup.TEST_GROUP.toString(), LogLevel.DEBUG,
+ false)))
+ .build();
+
+ try {
+ traceMonitor1.start();
+
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isTrue();
+
+ try {
+ traceMonitor2.start();
+
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP,
+ LogLevel.VERBOSE)).isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isTrue();
+ } finally {
+ traceMonitor2.stop(mWriter);
+ }
+
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isTrue();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isTrue();
+ } finally {
+ traceMonitor1.stop(mWriter);
+ }
+
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
+ .isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
+ .isTrue();
+ }
+
private enum TestProtoLogGroup implements IProtoLogGroup {
TEST_GROUP(true, true, false, "TEST_TAG");
diff --git a/tests/UsbTests/TEST_MAPPING b/tests/UsbTests/TEST_MAPPING
new file mode 100644
index 0000000..70134b8
--- /dev/null
+++ b/tests/UsbTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "UsbTests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/Android.bp b/tools/app_metadata_bundles/Android.bp
index a012dca..dced50d 100644
--- a/tools/app_metadata_bundles/Android.bp
+++ b/tools/app_metadata_bundles/Android.bp
@@ -13,6 +13,9 @@
srcs: [
"src/lib/java/**/*.java",
],
+ static_libs: [
+ "guava",
+ ],
}
java_binary_host {
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
index 9dd5531..c1c520e 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslConverter.java
@@ -16,7 +16,10 @@
package com.android.asllib;
+import com.android.asllib.marshallable.AndroidSafetyLabel;
+import com.android.asllib.marshallable.AndroidSafetyLabelFactory;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeConstants.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeConstants.java
deleted file mode 100644
index a0a7537..0000000
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeConstants.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.asllib;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Constants for determining valid {@link String} data types for usage within {@link SafetyLabels},
- * {@link DataCategory}, and {@link DataType}
- */
-public class DataTypeConstants {
- /** Data types for {@link DataCategoryConstants.CATEGORY_PERSONAL} */
- public static final String TYPE_NAME = "name";
-
- public static final String TYPE_EMAIL_ADDRESS = "email_address";
- public static final String TYPE_PHONE_NUMBER = "phone_number";
- public static final String TYPE_RACE_ETHNICITY = "race_ethnicity";
- public static final String TYPE_POLITICAL_OR_RELIGIOUS_BELIEFS =
- "political_or_religious_beliefs";
- public static final String TYPE_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY =
- "sexual_orientation_or_gender_identity";
- public static final String TYPE_PERSONAL_IDENTIFIERS = "personal_identifiers";
- public static final String TYPE_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_FINANCIAL} */
- public static final String TYPE_CARD_BANK_ACCOUNT = "card_bank_account";
-
- public static final String TYPE_PURCHASE_HISTORY = "purchase_history";
- public static final String TYPE_CREDIT_SCORE = "credit_score";
- public static final String TYPE_FINANCIAL_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_LOCATION} */
- public static final String TYPE_APPROX_LOCATION = "approx_location";
-
- public static final String TYPE_PRECISE_LOCATION = "precise_location";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_EMAIL_TEXT_MESSAGE} */
- public static final String TYPE_EMAILS = "emails";
-
- public static final String TYPE_TEXT_MESSAGES = "text_messages";
- public static final String TYPE_EMAIL_TEXT_MESSAGE_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_PHOTO_VIDEO} */
- public static final String TYPE_PHOTOS = "photos";
-
- public static final String TYPE_VIDEOS = "videos";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_AUDIO} */
- public static final String TYPE_SOUND_RECORDINGS = "sound_recordings";
-
- public static final String TYPE_MUSIC_FILES = "music_files";
- public static final String TYPE_AUDIO_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_STORAGE} */
- public static final String TYPE_FILES_DOCS = "files_docs";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_HEALTH_FITNESS} */
- public static final String TYPE_HEALTH = "health";
-
- public static final String TYPE_FITNESS = "fitness";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_CONTACTS} */
- public static final String TYPE_CONTACTS = "contacts";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_CALENDAR} */
- public static final String TYPE_CALENDAR = "calendar";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_IDENTIFIERS} */
- public static final String TYPE_IDENTIFIERS_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_APP_PERFORMANCE} */
- public static final String TYPE_CRASH_LOGS = "crash_logs";
-
- public static final String TYPE_PERFORMANCE_DIAGNOSTICS = "performance_diagnostics";
- public static final String TYPE_APP_PERFORMANCE_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_ACTIONS_IN_APP} */
- public static final String TYPE_USER_INTERACTION = "user_interaction";
-
- public static final String TYPE_IN_APP_SEARCH_HISTORY = "in_app_search_history";
- public static final String TYPE_INSTALLED_APPS = "installed_apps";
- public static final String TYPE_USER_GENERATED_CONTENT = "user_generated_content";
- public static final String TYPE_ACTIONS_IN_APP_OTHER = "other";
-
- /** Data types for {@link DataCategoryConstants.CATEGORY_SEARCH_AND_BROWSING} */
- public static final String TYPE_WEB_BROWSING_HISTORY = "web_browsing_history";
-
- /** Set of valid categories */
- public static final Set<String> VALID_TYPES =
- Collections.unmodifiableSet(
- new HashSet<>(
- Arrays.asList(
- TYPE_NAME,
- TYPE_EMAIL_ADDRESS,
- TYPE_PHONE_NUMBER,
- TYPE_RACE_ETHNICITY,
- TYPE_POLITICAL_OR_RELIGIOUS_BELIEFS,
- TYPE_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY,
- TYPE_PERSONAL_IDENTIFIERS,
- TYPE_OTHER,
- TYPE_CARD_BANK_ACCOUNT,
- TYPE_PURCHASE_HISTORY,
- TYPE_CREDIT_SCORE,
- TYPE_FINANCIAL_OTHER,
- TYPE_APPROX_LOCATION,
- TYPE_PRECISE_LOCATION,
- TYPE_EMAILS,
- TYPE_TEXT_MESSAGES,
- TYPE_EMAIL_TEXT_MESSAGE_OTHER,
- TYPE_PHOTOS,
- TYPE_VIDEOS,
- TYPE_SOUND_RECORDINGS,
- TYPE_MUSIC_FILES,
- TYPE_AUDIO_OTHER,
- TYPE_FILES_DOCS,
- TYPE_HEALTH,
- TYPE_FITNESS,
- TYPE_CONTACTS,
- TYPE_CALENDAR,
- TYPE_IDENTIFIERS_OTHER,
- TYPE_CRASH_LOGS,
- TYPE_PERFORMANCE_DIAGNOSTICS,
- TYPE_APP_PERFORMANCE_OTHER,
- TYPE_USER_INTERACTION,
- TYPE_IN_APP_SEARCH_HISTORY,
- TYPE_INSTALLED_APPS,
- TYPE_USER_GENERATED_CONTENT,
- TYPE_ACTIONS_IN_APP_OTHER,
- TYPE_WEB_BROWSING_HISTORY)));
-
- /** Returns {@link Set} of valid {@link String} category keys */
- public static Set<String> getValidDataTypes() {
- return VALID_TYPES;
- }
-
- private DataTypeConstants() {
- /* do nothing - hide constructor */
- }
-}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
similarity index 96%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
index cdb559b..112b92c 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabel.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
similarity index 96%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
index 3dc725b..b69c30f 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AndroidSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AndroidSafetyLabelFactory.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
similarity index 98%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
index f94b659..3f1ddeb 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfo.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
similarity index 74%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
index 26d94c1..59a437d 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AppInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AppInfoFactory.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
-import java.util.Arrays;
import java.util.List;
public class AppInfoFactory implements AslMarshallableFactory<AppInfo> {
@@ -37,31 +37,22 @@
String title = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_TITLE);
String description = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_DESCRIPTION);
- Boolean containsAds = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_CONTAINS_ADS);
- Boolean obeyAps = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_OBEY_APS);
+ Boolean containsAds = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_CONTAINS_ADS, true);
+ Boolean obeyAps = XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_OBEY_APS, true);
Boolean adsFingerprinting =
- XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_ADS_FINGERPRINTING);
+ XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_ADS_FINGERPRINTING, true);
Boolean securityFingerprinting =
- XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING);
+ XmlUtils.getBoolAttr(appInfoEle, XmlUtils.HR_ATTR_SECURITY_FINGERPRINTING, true);
String privacyPolicy = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_PRIVACY_POLICY);
List<String> securityEndpoints =
- Arrays.stream(
- appInfoEle
- .getAttribute(XmlUtils.HR_ATTR_SECURITY_ENDPOINTS)
- .split("\\|"))
- .toList();
+ XmlUtils.getPipelineSplitAttr(
+ appInfoEle, XmlUtils.HR_ATTR_SECURITY_ENDPOINTS, true);
List<String> firstPartyEndpoints =
- Arrays.stream(
- appInfoEle
- .getAttribute(XmlUtils.HR_ATTR_FIRST_PARTY_ENDPOINTS)
- .split("\\|"))
- .toList();
+ XmlUtils.getPipelineSplitAttr(
+ appInfoEle, XmlUtils.HR_ATTR_FIRST_PARTY_ENDPOINTS, true);
List<String> serviceProviderEndpoints =
- Arrays.stream(
- appInfoEle
- .getAttribute(XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS)
- .split("\\|"))
- .toList();
+ XmlUtils.getPipelineSplitAttr(
+ appInfoEle, XmlUtils.HR_ATTR_SERVICE_PROVIDER_ENDPOINTS, true);
String category = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_CATEGORY);
String email = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_EMAIL);
String website = XmlUtils.getStringAttr(appInfoEle, XmlUtils.HR_ATTR_WEBSITE, false);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
similarity index 95%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
index 4e64ab0..48747cc 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallable.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallable.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
similarity index 95%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
index b8f9f0e..a49b3e7 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/AslMarshallableFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/AslMarshallableFactory.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.MalformedXmlException;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
similarity index 91%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
index b9e06fb..4d67162 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategory.java
@@ -14,7 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.DataCategoryConstants;
+import com.android.asllib.util.DataTypeConstants;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
similarity index 62%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
index d2b6712..37d99e7 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataCategoryFactory.java
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+import com.android.asllib.util.DataTypeConstants;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
@@ -30,11 +32,17 @@
String categoryName = null;
Map<String, DataType> dataTypeMap = new LinkedHashMap<String, DataType>();
for (Element ele : elements) {
- categoryName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_CATEGORY);
- String dataTypeName = ele.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE);
- if (!DataTypeConstants.getValidDataTypes().contains(dataTypeName)) {
+ categoryName = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_DATA_CATEGORY, true);
+ String dataTypeName = XmlUtils.getStringAttr(ele, XmlUtils.HR_ATTR_DATA_TYPE, true);
+ if (!DataTypeConstants.getValidDataTypes().containsKey(categoryName)) {
throw new MalformedXmlException(
- String.format("Unrecognized data type name: %s", dataTypeName));
+ String.format("Unrecognized data category %s", categoryName));
+ }
+ if (!DataTypeConstants.getValidDataTypes().get(categoryName).contains(dataTypeName)) {
+ throw new MalformedXmlException(
+ String.format(
+ "Unrecognized data type name %s for category %s",
+ dataTypeName, categoryName));
}
dataTypeMap.put(
dataTypeName, new DataTypeFactory().createFromHrElements(XmlUtils.listOf(ele)));
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
similarity index 85%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
index 96ec93c..7516faf 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabels.java
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.DataCategoryConstants;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -41,24 +44,23 @@
}
/**
- * Returns the data accessed {@link Map} of {@link com.android.asllib.DataCategoryConstants} to
- * {@link DataCategory}
+ * Returns the data accessed {@link Map} of {@link DataCategoryConstants} to {@link
+ * DataCategory}
*/
public Map<String, DataCategory> getDataAccessed() {
return mDataAccessed;
}
/**
- * Returns the data collected {@link Map} of {@link com.android.asllib.DataCategoryConstants} to
- * {@link DataCategory}
+ * Returns the data collected {@link Map} of {@link DataCategoryConstants} to {@link
+ * DataCategory}
*/
public Map<String, DataCategory> getDataCollected() {
return mDataCollected;
}
/**
- * Returns the data shared {@link Map} of {@link com.android.asllib.DataCategoryConstants} to
- * {@link DataCategory}
+ * Returns the data shared {@link Map} of {@link DataCategoryConstants} to {@link DataCategory}
*/
public Map<String, DataCategory> getDataShared() {
return mDataShared;
@@ -70,7 +72,7 @@
Element dataLabelsEle =
XmlUtils.createPbundleEleWithName(doc, XmlUtils.OD_NAME_DATA_LABELS);
- maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_ACCESSED);
+ maybeAppendDataUsages(doc, dataLabelsEle, mDataAccessed, XmlUtils.OD_NAME_DATA_ACCESSED);
maybeAppendDataUsages(doc, dataLabelsEle, mDataCollected, XmlUtils.OD_NAME_DATA_COLLECTED);
maybeAppendDataUsages(doc, dataLabelsEle, mDataShared, XmlUtils.OD_NAME_DATA_SHARED);
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
similarity index 97%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
index 79edab7..dc77fd0 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataLabelsFactory.java
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
+import com.android.asllib.util.DataCategoryConstants;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
similarity index 98%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
index d011cfe..3471362 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataType.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataType.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
similarity index 67%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
index 27c1b59..ed434cd 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataTypeFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DataTypeFactory.java
@@ -14,30 +14,40 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
-import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.stream.Collectors;
public class DataTypeFactory implements AslMarshallableFactory<DataType> {
/** Creates a {@link DataType} from the human-readable DOM element. */
@Override
- public DataType createFromHrElements(List<Element> elements) {
+ public DataType createFromHrElements(List<Element> elements) throws MalformedXmlException {
Element hrDataTypeEle = XmlUtils.getSingleElement(elements);
String dataTypeName = hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_DATA_TYPE);
List<DataType.Purpose> purposes =
- Arrays.stream(hrDataTypeEle.getAttribute(XmlUtils.HR_ATTR_PURPOSES).split("\\|"))
+ XmlUtils.getPipelineSplitAttr(hrDataTypeEle, XmlUtils.HR_ATTR_PURPOSES, true)
+ .stream()
.map(DataType.Purpose::forString)
.collect(Collectors.toList());
+ if (purposes.isEmpty()) {
+ throw new MalformedXmlException(String.format("Found no purpose in: %s", dataTypeName));
+ }
+ if (new HashSet<>(purposes).size() != purposes.size()) {
+ throw new MalformedXmlException(
+ String.format("Found non-unique purposes in: %s", dataTypeName));
+ }
Boolean isCollectionOptional =
- XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL);
+ XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_COLLECTION_OPTIONAL, false);
Boolean isSharingOptional =
- XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL);
- Boolean ephemeral = XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL);
+ XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_IS_SHARING_OPTIONAL, false);
+ Boolean ephemeral = XmlUtils.getBoolAttr(hrDataTypeEle, XmlUtils.HR_ATTR_EPHEMERAL, false);
return new DataType(
dataTypeName, purposes, isCollectionOptional, isSharingOptional, ephemeral);
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
similarity index 98%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
index 44a5b12..382a1f0 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfo.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
similarity index 96%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
index 4961892..b5310ba 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DeveloperInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/DeveloperInfoFactory.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
similarity index 95%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
index 40ef48d..22c3fd8 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabels.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabels.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
similarity index 88%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
index ab81b1d..6bf8ef3 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SafetyLabelsFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SafetyLabelsFactory.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
@@ -40,7 +41,9 @@
.createFromHrElements(
XmlUtils.listOf(
XmlUtils.getSingleChildElement(
- safetyLabelsEle, XmlUtils.HR_TAG_DATA_LABELS)));
+ safetyLabelsEle,
+ XmlUtils.HR_TAG_DATA_LABELS,
+ false)));
return new SafetyLabels(version, dataLabels);
}
}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
similarity index 94%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
index 93d9c2b..595d748 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabel.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabel.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
similarity index 94%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
index c8c1c7b..f999559 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/SystemAppSafetyLabelFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/SystemAppSafetyLabelFactory.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
similarity index 95%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
index 88717b9..ddd3557 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfo.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfo.java
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
+
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
similarity index 95%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
index 13a7eb6..d9c2af4 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/TransparencyInfoFactory.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/marshallable/TransparencyInfoFactory.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.marshallable;
import com.android.asllib.util.AslgenUtil;
import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
import org.w3c.dom.Element;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryConstants.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataCategoryConstants.java
similarity index 94%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryConstants.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataCategoryConstants.java
index b364c8b..b5ae54c 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/DataCategoryConstants.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataCategoryConstants.java
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-package com.android.asllib;
+package com.android.asllib.util;
+import com.android.asllib.marshallable.DataCategory;
+import com.android.asllib.marshallable.DataType;
+import com.android.asllib.marshallable.SafetyLabels;
import java.util.Arrays;
import java.util.Collections;
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataTypeConstants.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataTypeConstants.java
new file mode 100644
index 0000000..358d575
--- /dev/null
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/DataTypeConstants.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.asllib.util;
+
+import com.android.asllib.marshallable.DataCategory;
+import com.android.asllib.marshallable.DataType;
+import com.android.asllib.marshallable.SafetyLabels;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Constants for determining valid {@link String} data types for usage within {@link SafetyLabels},
+ * {@link DataCategory}, and {@link DataType}
+ */
+public class DataTypeConstants {
+ /** Data types for {@link DataCategoryConstants.CATEGORY_PERSONAL} */
+ public static final String TYPE_NAME = "name";
+
+ public static final String TYPE_EMAIL_ADDRESS = "email_address";
+ public static final String TYPE_PHYSICAL_ADDRESS = "physical_address";
+ public static final String TYPE_PHONE_NUMBER = "phone_number";
+ public static final String TYPE_RACE_ETHNICITY = "race_ethnicity";
+ public static final String TYPE_POLITICAL_OR_RELIGIOUS_BELIEFS =
+ "political_or_religious_beliefs";
+ public static final String TYPE_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY =
+ "sexual_orientation_or_gender_identity";
+ public static final String TYPE_PERSONAL_IDENTIFIERS = "personal_identifiers";
+ public static final String TYPE_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_FINANCIAL} */
+ public static final String TYPE_CARD_BANK_ACCOUNT = "card_bank_account";
+
+ public static final String TYPE_PURCHASE_HISTORY = "purchase_history";
+ public static final String TYPE_CREDIT_SCORE = "credit_score";
+ public static final String TYPE_FINANCIAL_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_LOCATION} */
+ public static final String TYPE_APPROX_LOCATION = "approx_location";
+
+ public static final String TYPE_PRECISE_LOCATION = "precise_location";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_EMAIL_TEXT_MESSAGE} */
+ public static final String TYPE_EMAILS = "emails";
+
+ public static final String TYPE_TEXT_MESSAGES = "text_messages";
+ public static final String TYPE_EMAIL_TEXT_MESSAGE_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_PHOTO_VIDEO} */
+ public static final String TYPE_PHOTOS = "photos";
+
+ public static final String TYPE_VIDEOS = "videos";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_AUDIO} */
+ public static final String TYPE_SOUND_RECORDINGS = "sound_recordings";
+
+ public static final String TYPE_MUSIC_FILES = "music_files";
+ public static final String TYPE_AUDIO_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_STORAGE} */
+ public static final String TYPE_FILES_DOCS = "files_docs";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_HEALTH_FITNESS} */
+ public static final String TYPE_HEALTH = "health";
+
+ public static final String TYPE_FITNESS = "fitness";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_CONTACTS} */
+ public static final String TYPE_CONTACTS = "contacts";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_CALENDAR} */
+ public static final String TYPE_CALENDAR = "calendar";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_IDENTIFIERS} */
+ public static final String TYPE_IDENTIFIERS_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_APP_PERFORMANCE} */
+ public static final String TYPE_CRASH_LOGS = "crash_logs";
+
+ public static final String TYPE_PERFORMANCE_DIAGNOSTICS = "performance_diagnostics";
+ public static final String TYPE_APP_PERFORMANCE_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_ACTIONS_IN_APP} */
+ public static final String TYPE_USER_INTERACTION = "user_interaction";
+
+ public static final String TYPE_IN_APP_SEARCH_HISTORY = "in_app_search_history";
+ public static final String TYPE_INSTALLED_APPS = "installed_apps";
+ public static final String TYPE_USER_GENERATED_CONTENT = "user_generated_content";
+ public static final String TYPE_ACTIONS_IN_APP_OTHER = "other";
+
+ /** Data types for {@link DataCategoryConstants.CATEGORY_SEARCH_AND_BROWSING} */
+ public static final String TYPE_WEB_BROWSING_HISTORY = "web_browsing_history";
+
+ /** Set of valid categories */
+ private static final Map<String, Set<String>> VALID_DATA_TYPES =
+ new ImmutableMap.Builder<String, Set<String>>()
+ .put(
+ DataCategoryConstants.CATEGORY_PERSONAL,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_NAME,
+ DataTypeConstants.TYPE_EMAIL_ADDRESS,
+ DataTypeConstants.TYPE_PHYSICAL_ADDRESS,
+ DataTypeConstants.TYPE_PHONE_NUMBER,
+ DataTypeConstants.TYPE_RACE_ETHNICITY,
+ DataTypeConstants.TYPE_POLITICAL_OR_RELIGIOUS_BELIEFS,
+ DataTypeConstants.TYPE_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY,
+ DataTypeConstants.TYPE_PERSONAL_IDENTIFIERS,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_FINANCIAL,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_CARD_BANK_ACCOUNT,
+ DataTypeConstants.TYPE_PURCHASE_HISTORY,
+ DataTypeConstants.TYPE_CREDIT_SCORE,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_LOCATION,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_APPROX_LOCATION,
+ DataTypeConstants.TYPE_PRECISE_LOCATION))
+ .put(
+ DataCategoryConstants.CATEGORY_EMAIL_TEXT_MESSAGE,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_EMAILS,
+ DataTypeConstants.TYPE_TEXT_MESSAGES,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_PHOTO_VIDEO,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_PHOTOS, DataTypeConstants.TYPE_VIDEOS))
+ .put(
+ DataCategoryConstants.CATEGORY_AUDIO,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_SOUND_RECORDINGS,
+ DataTypeConstants.TYPE_MUSIC_FILES,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_STORAGE,
+ ImmutableSet.of(DataTypeConstants.TYPE_FILES_DOCS))
+ .put(
+ DataCategoryConstants.CATEGORY_HEALTH_FITNESS,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_HEALTH, DataTypeConstants.TYPE_FITNESS))
+ .put(
+ DataCategoryConstants.CATEGORY_CONTACTS,
+ ImmutableSet.of(DataTypeConstants.TYPE_CONTACTS))
+ .put(
+ DataCategoryConstants.CATEGORY_CALENDAR,
+ ImmutableSet.of(DataTypeConstants.TYPE_CALENDAR))
+ .put(
+ DataCategoryConstants.CATEGORY_IDENTIFIERS,
+ ImmutableSet.of(DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_APP_PERFORMANCE,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_CRASH_LOGS,
+ DataTypeConstants.TYPE_PERFORMANCE_DIAGNOSTICS,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_ACTIONS_IN_APP,
+ ImmutableSet.of(
+ DataTypeConstants.TYPE_USER_INTERACTION,
+ DataTypeConstants.TYPE_IN_APP_SEARCH_HISTORY,
+ DataTypeConstants.TYPE_INSTALLED_APPS,
+ DataTypeConstants.TYPE_USER_GENERATED_CONTENT,
+ DataTypeConstants.TYPE_OTHER))
+ .put(
+ DataCategoryConstants.CATEGORY_SEARCH_AND_BROWSING,
+ ImmutableSet.of(DataTypeConstants.TYPE_WEB_BROWSING_HISTORY))
+ .buildOrThrow();
+
+ /** Returns {@link Set} of valid {@link String} category keys */
+ public static Map<String, Set<String>> getValidDataTypes() {
+ return VALID_DATA_TYPES;
+ }
+
+ private DataTypeConstants() {
+ /* do nothing - hide constructor */
+ }
+}
diff --git a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
similarity index 91%
rename from tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
rename to tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
index cc8fe79..691f92f 100644
--- a/tools/app_metadata_bundles/src/lib/java/com/android/asllib/XmlUtils.java
+++ b/tools/app_metadata_bundles/src/lib/java/com/android/asllib/util/XmlUtils.java
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-package com.android.asllib;
-
-import com.android.asllib.util.MalformedXmlException;
+package com.android.asllib.util;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -259,21 +257,42 @@
}
/** Tries getting required version attribute and throws exception if it doesn't exist */
- public static Long tryGetVersion(Element ele) {
+ public static Long tryGetVersion(Element ele) throws MalformedXmlException {
long version;
try {
version = Long.parseLong(ele.getAttribute(XmlUtils.HR_ATTR_VERSION));
} catch (Exception e) {
- throw new IllegalArgumentException(
+ throw new MalformedXmlException(
String.format(
"Malformed or missing required version in: %s", ele.getTagName()));
}
return version;
}
- /** Gets an optional Boolean attribute. */
- public static Boolean getBoolAttr(Element ele, String attrName) {
- return XmlUtils.fromString(ele.getAttribute(attrName));
+ /** Gets a pipeline-split attribute. */
+ public static List<String> getPipelineSplitAttr(Element ele, String attrName, boolean required)
+ throws MalformedXmlException {
+ List<String> list = Arrays.stream(ele.getAttribute(attrName).split("\\|")).toList();
+ if ((list.isEmpty() || list.get(0).isEmpty()) && required) {
+ throw new MalformedXmlException(
+ String.format(
+ "Delimited string %s was required but missing, in %s.",
+ attrName, ele.getTagName()));
+ }
+ return list;
+ }
+
+ /** Gets a Boolean attribute. */
+ public static Boolean getBoolAttr(Element ele, String attrName, boolean required)
+ throws MalformedXmlException {
+ Boolean b = XmlUtils.fromString(ele.getAttribute(attrName));
+ if (b == null && required) {
+ throw new MalformedXmlException(
+ String.format(
+ "Boolean %s was required but missing, in %s.",
+ attrName, ele.getTagName()));
+ }
+ return b;
}
/** Gets a required String attribute. */
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java
deleted file mode 100644
index 3026f8b..0000000
--- a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AslgenTests.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2017 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.aslgen;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.asllib.AndroidSafetyLabel;
-import com.android.asllib.AslConverter;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.w3c.dom.Document;
-import org.xml.sax.SAXException;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-
-@RunWith(JUnit4.class)
-public class AslgenTests {
- private static final String VALID_MAPPINGS_PATH = "com/android/aslgen/validmappings";
- private static final List<String> VALID_MAPPINGS_SUBDIRS = List.of("location", "contacts");
- private static final String HR_XML_FILENAME = "hr.xml";
- private static final String OD_XML_FILENAME = "od.xml";
-
- /** Logic for setting up tests (empty if not yet needed). */
- public static void main(String[] params) throws Exception {}
-
- /** Tests valid mappings between HR and OD. */
- @Test
- public void testValidMappings() throws Exception {
- System.out.println("start testing valid mappings.");
-
- for (String subdir : VALID_MAPPINGS_SUBDIRS) {
- Path hrPath = Paths.get(VALID_MAPPINGS_PATH, subdir, HR_XML_FILENAME);
- Path odPath = Paths.get(VALID_MAPPINGS_PATH, subdir, OD_XML_FILENAME);
-
- System.out.println("hr path: " + hrPath.toString());
- System.out.println("od path: " + odPath.toString());
-
- InputStream hrStream =
- getClass().getClassLoader().getResourceAsStream(hrPath.toString());
- String hrContents = new String(hrStream.readAllBytes(), StandardCharsets.UTF_8);
- InputStream odStream =
- getClass().getClassLoader().getResourceAsStream(odPath.toString());
- String odContents = new String(odStream.readAllBytes(), StandardCharsets.UTF_8);
- AndroidSafetyLabel asl =
- AslConverter.readFromString(hrContents, AslConverter.Format.HUMAN_READABLE);
- String out = AslConverter.getXmlAsString(asl, AslConverter.Format.ON_DEVICE);
- System.out.println("out: " + out);
-
- assertEquals(getFormattedXml(out), getFormattedXml(odContents));
- }
- }
-
- private static String getFormattedXml(String xmlStr)
- throws ParserConfigurationException, IOException, SAXException, TransformerException {
- InputStream stream = new ByteArrayInputStream(xmlStr.getBytes(StandardCharsets.UTF_8));
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- factory.setNamespaceAware(true);
- Document document = factory.newDocumentBuilder().parse(stream);
-
- TransformerFactory transformerFactory = TransformerFactory.newInstance();
- Transformer transformer = transformerFactory.newTransformer();
- transformer.setOutputProperty(OutputKeys.INDENT, "yes");
- transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
- transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
-
- ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- StreamResult streamResult = new StreamResult(outStream); // out
- DOMSource domSource = new DOMSource(document);
- transformer.transform(domSource, streamResult);
-
- return outStream.toString(StandardCharsets.UTF_8);
- }
-}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AllTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
similarity index 67%
rename from tools/app_metadata_bundles/src/test/java/com/android/aslgen/AllTests.java
rename to tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
index 7ebb7a1..03e8ac6 100644
--- a/tools/app_metadata_bundles/src/test/java/com/android/aslgen/AllTests.java
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AllTests.java
@@ -14,7 +14,12 @@
* limitations under the License.
*/
-package com.android.aslgen;
+package com.android.asllib;
+
+import com.android.asllib.marshallable.AndroidSafetyLabelTest;
+import com.android.asllib.marshallable.DataCategoryTest;
+import com.android.asllib.marshallable.DataLabelsTest;
+import com.android.asllib.marshallable.DeveloperInfoTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@@ -22,5 +27,9 @@
@RunWith(Suite.class)
@Suite.SuiteClasses({
AslgenTests.class,
+ AndroidSafetyLabelTest.class,
+ DeveloperInfoTest.class,
+ DataCategoryTest.class,
+ DataLabelsTest.class,
})
public class AllTests {}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
new file mode 100644
index 0000000..5f43008
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/AslgenTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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.asllib;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.asllib.marshallable.AndroidSafetyLabel;
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class AslgenTests {
+ private static final String VALID_MAPPINGS_PATH = "com/android/asllib/validmappings";
+ private static final List<String> VALID_MAPPINGS_SUBDIRS = List.of("location", "contacts");
+ private static final String HR_XML_FILENAME = "hr.xml";
+ private static final String OD_XML_FILENAME = "od.xml";
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ /** Tests valid mappings between HR and OD. */
+ @Test
+ public void testValidMappings() throws Exception {
+ System.out.println("start testing valid mappings.");
+
+ for (String subdir : VALID_MAPPINGS_SUBDIRS) {
+ Path hrPath = Paths.get(VALID_MAPPINGS_PATH, subdir, HR_XML_FILENAME);
+ Path odPath = Paths.get(VALID_MAPPINGS_PATH, subdir, OD_XML_FILENAME);
+
+ System.out.println("hr path: " + hrPath.toString());
+ System.out.println("od path: " + odPath.toString());
+
+ InputStream hrStream =
+ getClass().getClassLoader().getResourceAsStream(hrPath.toString());
+ String hrContents = new String(hrStream.readAllBytes(), StandardCharsets.UTF_8);
+ InputStream odStream =
+ getClass().getClassLoader().getResourceAsStream(odPath.toString());
+ String odContents = new String(odStream.readAllBytes(), StandardCharsets.UTF_8);
+ AndroidSafetyLabel asl =
+ AslConverter.readFromString(hrContents, AslConverter.Format.HUMAN_READABLE);
+ String out = AslConverter.getXmlAsString(asl, AslConverter.Format.ON_DEVICE);
+ System.out.println("out: " + out);
+
+ assertEquals(
+ TestUtils.getFormattedXml(out, false),
+ TestUtils.getFormattedXml(odContents, false));
+ }
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
new file mode 100644
index 0000000..0137007
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AndroidSafetyLabelTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 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.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class AndroidSafetyLabelTest {
+ private static final String ANDROID_SAFETY_LABEL_HR_PATH =
+ "com/android/asllib/androidsafetylabel/hr";
+ private static final String ANDROID_SAFETY_LABEL_OD_PATH =
+ "com/android/asllib/androidsafetylabel/od";
+
+ private static final String MISSING_VERSION_FILE_NAME = "missing-version.xml";
+ private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml";
+ private static final String WITH_SAFETY_LABELS_FILE_NAME = "with-safety-labels.xml";
+ private static final String WITH_SYSTEM_APP_SAFETY_LABEL_FILE_NAME =
+ "with-system-app-safety-label.xml";
+ private static final String WITH_TRANSPARENCY_INFO_FILE_NAME = "with-transparency-info.xml";
+
+ private Document mDoc = null;
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for android safety label missing version. */
+ @Test
+ public void testAndroidSafetyLabelMissingVersion() throws Exception {
+ System.out.println("starting testAndroidSafetyLabelMissingVersion.");
+ hrToOdExpectException(MISSING_VERSION_FILE_NAME);
+ }
+
+ /** Test for android safety label valid empty. */
+ @Test
+ public void testAndroidSafetyLabelValidEmptyFile() throws Exception {
+ System.out.println("starting testAndroidSafetyLabelValidEmptyFile.");
+ testHrToOdAndroidSafetyLabel(VALID_EMPTY_FILE_NAME);
+ }
+
+ /** Test for android safety label with safety labels. */
+ @Test
+ public void testAndroidSafetyLabelWithSafetyLabels() throws Exception {
+ System.out.println("starting testAndroidSafetyLabelWithSafetyLabels.");
+ testHrToOdAndroidSafetyLabel(WITH_SAFETY_LABELS_FILE_NAME);
+ }
+
+ /** Test for android safety label with system app safety label. */
+ @Test
+ public void testAndroidSafetyLabelWithSystemAppSafetyLabel() throws Exception {
+ System.out.println("starting testAndroidSafetyLabelWithSystemAppSafetyLabel.");
+ testHrToOdAndroidSafetyLabel(WITH_SYSTEM_APP_SAFETY_LABEL_FILE_NAME);
+ }
+
+ /** Test for android safety label with transparency info. */
+ @Test
+ public void testAndroidSafetyLabelWithTransparencyInfo() throws Exception {
+ System.out.println("starting testAndroidSafetyLabelWithTransparencyInfo.");
+ testHrToOdAndroidSafetyLabel(WITH_TRANSPARENCY_INFO_FILE_NAME);
+ }
+
+ private void hrToOdExpectException(String fileName) {
+ TestUtils.hrToOdExpectException(
+ new AndroidSafetyLabelFactory(), ANDROID_SAFETY_LABEL_HR_PATH, fileName);
+ }
+
+ private void testHrToOdAndroidSafetyLabel(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new AndroidSafetyLabelFactory(),
+ ANDROID_SAFETY_LABEL_HR_PATH,
+ ANDROID_SAFETY_LABEL_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
new file mode 100644
index 0000000..a015e2e
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/AppInfoTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 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.asllib.marshallable;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+import java.nio.file.Paths;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class AppInfoTest {
+ private static final String APP_INFO_HR_PATH = "com/android/asllib/appinfo/hr";
+ private static final String APP_INFO_OD_PATH = "com/android/asllib/appinfo/od";
+ public static final List<String> REQUIRED_FIELD_NAMES =
+ List.of(
+ "title",
+ "description",
+ "containsAds",
+ "obeyAps",
+ "adsFingerprinting",
+ "securityFingerprinting",
+ "privacyPolicy",
+ "securityEndpoints",
+ "firstPartyEndpoints",
+ "serviceProviderEndpoints",
+ "category",
+ "email");
+ public static final List<String> OPTIONAL_FIELD_NAMES = List.of("website");
+
+ private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
+
+ private Document mDoc = null;
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for all fields valid. */
+ @Test
+ public void testAllFieldsValid() throws Exception {
+ System.out.println("starting testAllFieldsValid.");
+ testHrToOdAppInfo(ALL_FIELDS_VALID_FILE_NAME);
+ }
+
+ /** Tests missing required fields fails. */
+ @Test
+ public void testMissingRequiredFields() throws Exception {
+ System.out.println("Starting testMissingRequiredFields");
+ for (String reqField : REQUIRED_FIELD_NAMES) {
+ System.out.println("testing missing required field: " + reqField);
+ var appInfoEle =
+ TestUtils.getElementsFromResource(
+ Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ appInfoEle.get(0).removeAttribute(reqField);
+
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new AppInfoFactory().createFromHrElements(appInfoEle));
+ }
+ }
+
+ /** Tests missing optional fields passes. */
+ @Test
+ public void testMissingOptionalFields() throws Exception {
+ for (String optField : OPTIONAL_FIELD_NAMES) {
+ var ele =
+ TestUtils.getElementsFromResource(
+ Paths.get(APP_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ ele.get(0).removeAttribute(optField);
+ AppInfo appInfo = new AppInfoFactory().createFromHrElements(ele);
+ appInfo.toOdDomElements(mDoc);
+ }
+ }
+
+ private void testHrToOdAppInfo(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc, new AppInfoFactory(), APP_INFO_HR_PATH, APP_INFO_OD_PATH, fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java
new file mode 100644
index 0000000..822f175
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataCategoryTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2017 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.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class DataCategoryTest {
+ private static final String DATA_CATEGORY_HR_PATH = "com/android/asllib/datacategory/hr";
+ private static final String DATA_CATEGORY_OD_PATH = "com/android/asllib/datacategory/od";
+
+ private static final String VALID_PERSONAL_FILE_NAME = "data-category-personal.xml";
+ private static final String VALID_PARTIAL_PERSONAL_FILE_NAME =
+ "data-category-personal-partial.xml";
+ private static final String VALID_FINANCIAL_FILE_NAME = "data-category-financial.xml";
+ private static final String VALID_LOCATION_FILE_NAME = "data-category-location.xml";
+ private static final String VALID_EMAIL_TEXT_MESSAGE_FILE_NAME =
+ "data-category-email-text-message.xml";
+ private static final String VALID_PHOTO_VIDEO_FILE_NAME = "data-category-photo-video.xml";
+ private static final String VALID_AUDIO_FILE_NAME = "data-category-audio.xml";
+ private static final String VALID_STORAGE_FILE_NAME = "data-category-storage.xml";
+ private static final String VALID_HEALTH_FITNESS_FILE_NAME = "data-category-health-fitness.xml";
+ private static final String VALID_CONTACTS_FILE_NAME = "data-category-contacts.xml";
+ private static final String VALID_CALENDAR_FILE_NAME = "data-category-calendar.xml";
+ private static final String VALID_IDENTIFIERS_FILE_NAME = "data-category-identifiers.xml";
+ private static final String VALID_APP_PERFORMANCE_FILE_NAME =
+ "data-category-app-performance.xml";
+ private static final String VALID_ACTIONS_IN_APP_FILE_NAME = "data-category-actions-in-app.xml";
+ private static final String VALID_SEARCH_AND_BROWSING_FILE_NAME =
+ "data-category-search-and-browsing.xml";
+
+ private static final String EMPTY_PURPOSE_PERSONAL_FILE_NAME =
+ "data-category-personal-empty-purpose.xml";
+ private static final String MISSING_PURPOSE_PERSONAL_FILE_NAME =
+ "data-category-personal-missing-purpose.xml";
+ private static final String UNRECOGNIZED_TYPE_PERSONAL_FILE_NAME =
+ "data-category-personal-unrecognized-type.xml";
+ private static final String UNRECOGNIZED_CATEGORY_FILE_NAME = "data-category-unrecognized.xml";
+
+ private Document mDoc = null;
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for data category personal. */
+ @Test
+ public void testDataCategoryPersonal() throws Exception {
+ System.out.println("starting testDataCategoryPersonal.");
+ testHrToOdDataCategory(VALID_PERSONAL_FILE_NAME);
+ }
+
+ /** Test for data category financial. */
+ @Test
+ public void testDataCategoryFinancial() throws Exception {
+ System.out.println("starting testDataCategoryFinancial.");
+ testHrToOdDataCategory(VALID_FINANCIAL_FILE_NAME);
+ }
+
+ /** Test for data category location. */
+ @Test
+ public void testDataCategoryLocation() throws Exception {
+ System.out.println("starting testDataCategoryLocation.");
+ testHrToOdDataCategory(VALID_LOCATION_FILE_NAME);
+ }
+
+ /** Test for data category email text message. */
+ @Test
+ public void testDataCategoryEmailTextMessage() throws Exception {
+ System.out.println("starting testDataCategoryEmailTextMessage.");
+ testHrToOdDataCategory(VALID_EMAIL_TEXT_MESSAGE_FILE_NAME);
+ }
+
+ /** Test for data category photo video. */
+ @Test
+ public void testDataCategoryPhotoVideo() throws Exception {
+ System.out.println("starting testDataCategoryPhotoVideo.");
+ testHrToOdDataCategory(VALID_PHOTO_VIDEO_FILE_NAME);
+ }
+
+ /** Test for data category audio. */
+ @Test
+ public void testDataCategoryAudio() throws Exception {
+ System.out.println("starting testDataCategoryAudio.");
+ testHrToOdDataCategory(VALID_AUDIO_FILE_NAME);
+ }
+
+ /** Test for data category storage. */
+ @Test
+ public void testDataCategoryStorage() throws Exception {
+ System.out.println("starting testDataCategoryStorage.");
+ testHrToOdDataCategory(VALID_STORAGE_FILE_NAME);
+ }
+
+ /** Test for data category health fitness. */
+ @Test
+ public void testDataCategoryHealthFitness() throws Exception {
+ System.out.println("starting testDataCategoryHealthFitness.");
+ testHrToOdDataCategory(VALID_HEALTH_FITNESS_FILE_NAME);
+ }
+
+ /** Test for data category contacts. */
+ @Test
+ public void testDataCategoryContacts() throws Exception {
+ System.out.println("starting testDataCategoryContacts.");
+ testHrToOdDataCategory(VALID_CONTACTS_FILE_NAME);
+ }
+
+ /** Test for data category calendar. */
+ @Test
+ public void testDataCategoryCalendar() throws Exception {
+ System.out.println("starting testDataCategoryCalendar.");
+ testHrToOdDataCategory(VALID_CALENDAR_FILE_NAME);
+ }
+
+ /** Test for data category identifiers. */
+ @Test
+ public void testDataCategoryIdentifiers() throws Exception {
+ System.out.println("starting testDataCategoryIdentifiers.");
+ testHrToOdDataCategory(VALID_IDENTIFIERS_FILE_NAME);
+ }
+
+ /** Test for data category app performance. */
+ @Test
+ public void testDataCategoryAppPerformance() throws Exception {
+ System.out.println("starting testDataCategoryAppPerformance.");
+ testHrToOdDataCategory(VALID_APP_PERFORMANCE_FILE_NAME);
+ }
+
+ /** Test for data category actions in app. */
+ @Test
+ public void testDataCategoryActionsInApp() throws Exception {
+ System.out.println("starting testDataCategoryActionsInApp.");
+ testHrToOdDataCategory(VALID_ACTIONS_IN_APP_FILE_NAME);
+ }
+
+ /** Test for data category search and browsing. */
+ @Test
+ public void testDataCategorySearchAndBrowsing() throws Exception {
+ System.out.println("starting testDataCategorySearchAndBrowsing.");
+ testHrToOdDataCategory(VALID_SEARCH_AND_BROWSING_FILE_NAME);
+ }
+
+ /** Test for data category search and browsing. */
+ @Test
+ public void testMissingOptionalsAllowed() throws Exception {
+ System.out.println("starting testMissingOptionalsAllowed.");
+ testHrToOdDataCategory(VALID_PARTIAL_PERSONAL_FILE_NAME);
+ }
+
+ /** Test for empty purposes. */
+ @Test
+ public void testEmptyPurposesNotAllowed() throws Exception {
+ System.out.println("starting testEmptyPurposesNotAllowed.");
+ hrToOdExpectException(EMPTY_PURPOSE_PERSONAL_FILE_NAME);
+ }
+
+ /** Test for missing purposes. */
+ @Test
+ public void testMissingPurposesNotAllowed() throws Exception {
+ System.out.println("starting testMissingPurposesNotAllowed.");
+ hrToOdExpectException(MISSING_PURPOSE_PERSONAL_FILE_NAME);
+ }
+
+ /** Test for unrecognized type. */
+ @Test
+ public void testUnrecognizedTypeNotAllowed() throws Exception {
+ System.out.println("starting testUnrecognizedTypeNotAllowed.");
+ hrToOdExpectException(UNRECOGNIZED_TYPE_PERSONAL_FILE_NAME);
+ }
+
+ /** Test for unrecognized category. */
+ @Test
+ public void testUnrecognizedCategoryNotAllowed() throws Exception {
+ System.out.println("starting testUnrecognizedCategoryNotAllowed.");
+ hrToOdExpectException(UNRECOGNIZED_CATEGORY_FILE_NAME);
+ }
+
+ private void hrToOdExpectException(String fileName) {
+ TestUtils.hrToOdExpectException(new DataCategoryFactory(), DATA_CATEGORY_HR_PATH, fileName);
+ }
+
+ private void testHrToOdDataCategory(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new DataCategoryFactory(),
+ DATA_CATEGORY_HR_PATH,
+ DATA_CATEGORY_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
new file mode 100644
index 0000000..2be447e
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DataLabelsTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 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.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class DataLabelsTest {
+ private static final String DATA_LABELS_HR_PATH = "com/android/asllib/datalabels/hr";
+ private static final String DATA_LABELS_OD_PATH = "com/android/asllib/datalabels/od";
+
+ private static final String ACCESSED_VALID_BOOL_FILE_NAME =
+ "data-labels-accessed-valid-bool.xml";
+ private static final String ACCESSED_INVALID_BOOL_FILE_NAME =
+ "data-labels-accessed-invalid-bool.xml";
+ private static final String COLLECTED_VALID_BOOL_FILE_NAME =
+ "data-labels-collected-valid-bool.xml";
+ private static final String COLLECTED_INVALID_BOOL_FILE_NAME =
+ "data-labels-collected-invalid-bool.xml";
+ private static final String SHARED_VALID_BOOL_FILE_NAME = "data-labels-shared-valid-bool.xml";
+ private static final String SHARED_INVALID_BOOL_FILE_NAME =
+ "data-labels-shared-invalid-bool.xml";
+
+ private Document mDoc = null;
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for data labels accessed valid bool. */
+ @Test
+ public void testDataLabelsAccessedValidBool() throws Exception {
+ System.out.println("starting testDataLabelsAccessedValidBool.");
+ testHrToOdDataLabels(ACCESSED_VALID_BOOL_FILE_NAME);
+ }
+
+ /** Test for data labels accessed invalid bool. */
+ @Test
+ public void testDataLabelsAccessedInvalidBool() throws Exception {
+ System.out.println("starting testDataLabelsAccessedInvalidBool.");
+ hrToOdExpectException(ACCESSED_INVALID_BOOL_FILE_NAME);
+ }
+
+ /** Test for data labels collected valid bool. */
+ @Test
+ public void testDataLabelsCollectedValidBool() throws Exception {
+ System.out.println("starting testDataLabelsCollectedValidBool.");
+ testHrToOdDataLabels(COLLECTED_VALID_BOOL_FILE_NAME);
+ }
+
+ /** Test for data labels collected invalid bool. */
+ @Test
+ public void testDataLabelsCollectedInvalidBool() throws Exception {
+ System.out.println("starting testDataLabelsCollectedInvalidBool.");
+ hrToOdExpectException(COLLECTED_INVALID_BOOL_FILE_NAME);
+ }
+
+ /** Test for data labels shared valid bool. */
+ @Test
+ public void testDataLabelsSharedValidBool() throws Exception {
+ System.out.println("starting testDataLabelsSharedValidBool.");
+ testHrToOdDataLabels(SHARED_VALID_BOOL_FILE_NAME);
+ }
+
+ /** Test for data labels shared invalid bool. */
+ @Test
+ public void testDataLabelsSharedInvalidBool() throws Exception {
+ System.out.println("starting testDataLabelsSharedInvalidBool.");
+ hrToOdExpectException(SHARED_INVALID_BOOL_FILE_NAME);
+ }
+
+ private void hrToOdExpectException(String fileName) {
+ TestUtils.hrToOdExpectException(new DataLabelsFactory(), DATA_LABELS_HR_PATH, fileName);
+ }
+
+ private void testHrToOdDataLabels(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc, new DataLabelsFactory(), DATA_LABELS_HR_PATH, DATA_LABELS_OD_PATH, fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
new file mode 100644
index 0000000..ff8346a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/DeveloperInfoTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 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.asllib.marshallable;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.asllib.testutils.TestUtils;
+import com.android.asllib.util.MalformedXmlException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+import java.nio.file.Paths;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class DeveloperInfoTest {
+ private static final String DEVELOPER_INFO_HR_PATH = "com/android/asllib/developerinfo/hr";
+ private static final String DEVELOPER_INFO_OD_PATH = "com/android/asllib/developerinfo/od";
+ public static final List<String> REQUIRED_FIELD_NAMES =
+ List.of("address", "countryRegion", "email", "name", "relationship");
+ public static final List<String> OPTIONAL_FIELD_NAMES = List.of("website", "registryId");
+
+ private static final String ALL_FIELDS_VALID_FILE_NAME = "all-fields-valid.xml";
+
+ private Document mDoc = null;
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for all fields valid. */
+ @Test
+ public void testAllFieldsValid() throws Exception {
+ System.out.println("starting testAllFieldsValid.");
+ testHrToOdDeveloperInfo(ALL_FIELDS_VALID_FILE_NAME);
+ }
+
+ /** Tests missing required fields fails. */
+ @Test
+ public void testMissingRequiredFields() throws Exception {
+ System.out.println("Starting testMissingRequiredFields");
+ for (String reqField : REQUIRED_FIELD_NAMES) {
+ System.out.println("testing missing required field: " + reqField);
+ var developerInfoEle =
+ TestUtils.getElementsFromResource(
+ Paths.get(DEVELOPER_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ developerInfoEle.get(0).removeAttribute(reqField);
+
+ assertThrows(
+ MalformedXmlException.class,
+ () -> new DeveloperInfoFactory().createFromHrElements(developerInfoEle));
+ }
+ }
+
+ /** Tests missing optional fields passes. */
+ @Test
+ public void testMissingOptionalFields() throws Exception {
+ for (String optField : OPTIONAL_FIELD_NAMES) {
+ var developerInfoEle =
+ TestUtils.getElementsFromResource(
+ Paths.get(DEVELOPER_INFO_HR_PATH, ALL_FIELDS_VALID_FILE_NAME));
+ developerInfoEle.get(0).removeAttribute(optField);
+ DeveloperInfo developerInfo =
+ new DeveloperInfoFactory().createFromHrElements(developerInfoEle);
+ developerInfo.toOdDomElements(mDoc);
+ }
+ }
+
+ private void testHrToOdDeveloperInfo(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new DeveloperInfoFactory(),
+ DEVELOPER_INFO_HR_PATH,
+ DEVELOPER_INFO_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
new file mode 100644
index 0000000..b62620e
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SafetyLabelsTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 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.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class SafetyLabelsTest {
+ private static final String SAFETY_LABELS_HR_PATH = "com/android/asllib/safetylabels/hr";
+ private static final String SAFETY_LABELS_OD_PATH = "com/android/asllib/safetylabels/od";
+
+ private static final String MISSING_VERSION_FILE_NAME = "missing-version.xml";
+ private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml";
+ private static final String WITH_DATA_LABELS_FILE_NAME = "with-data-labels.xml";
+
+ private Document mDoc = null;
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for safety labels missing version. */
+ @Test
+ public void testSafetyLabelsMissingVersion() throws Exception {
+ System.out.println("starting testSafetyLabelsMissingVersion.");
+ hrToOdExpectException(MISSING_VERSION_FILE_NAME);
+ }
+
+ /** Test for safety labels valid empty. */
+ @Test
+ public void testSafetyLabelsValidEmptyFile() throws Exception {
+ System.out.println("starting testSafetyLabelsValidEmptyFile.");
+ testHrToOdSafetyLabels(VALID_EMPTY_FILE_NAME);
+ }
+
+ /** Test for safety labels with data labels. */
+ @Test
+ public void testSafetyLabelsWithDataLabels() throws Exception {
+ System.out.println("starting testSafetyLabelsWithDataLabels.");
+ testHrToOdSafetyLabels(WITH_DATA_LABELS_FILE_NAME);
+ }
+
+ private void hrToOdExpectException(String fileName) {
+ TestUtils.hrToOdExpectException(new SafetyLabelsFactory(), SAFETY_LABELS_HR_PATH, fileName);
+ }
+
+ private void testHrToOdSafetyLabels(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new SafetyLabelsFactory(),
+ SAFETY_LABELS_HR_PATH,
+ SAFETY_LABELS_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
new file mode 100644
index 0000000..191091a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/SystemAppSafetyLabelTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class SystemAppSafetyLabelTest {
+ private static final String SYSTEM_APP_SAFETY_LABEL_HR_PATH =
+ "com/android/asllib/systemappsafetylabel/hr";
+ private static final String SYSTEM_APP_SAFETY_LABEL_OD_PATH =
+ "com/android/asllib/systemappsafetylabel/od";
+
+ private static final String VALID_FILE_NAME = "valid.xml";
+ private static final String MISSING_URL_FILE_NAME = "missing-url.xml";
+
+ private Document mDoc = null;
+
+ /** Logic for setting up tests (empty if not yet needed). */
+ public static void main(String[] params) throws Exception {}
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for valid. */
+ @Test
+ public void testValid() throws Exception {
+ System.out.println("starting testValid.");
+ testHrToOdSystemAppSafetyLabel(VALID_FILE_NAME);
+ }
+
+ /** Tests missing url. */
+ @Test
+ public void testMissingUrl() throws Exception {
+ System.out.println("starting testMissingUrl.");
+ hrToOdExpectException(MISSING_URL_FILE_NAME);
+ }
+
+ private void hrToOdExpectException(String fileName) {
+ TestUtils.hrToOdExpectException(
+ new SystemAppSafetyLabelFactory(), SYSTEM_APP_SAFETY_LABEL_HR_PATH, fileName);
+ }
+
+ private void testHrToOdSystemAppSafetyLabel(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new SystemAppSafetyLabelFactory(),
+ SYSTEM_APP_SAFETY_LABEL_HR_PATH,
+ SYSTEM_APP_SAFETY_LABEL_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
new file mode 100644
index 0000000..56503f7
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/marshallable/TransparencyInfoTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 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.asllib.marshallable;
+
+import com.android.asllib.testutils.TestUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Document;
+
+@RunWith(JUnit4.class)
+public class TransparencyInfoTest {
+ private static final String TRANSPARENCY_INFO_HR_PATH =
+ "com/android/asllib/transparencyinfo/hr";
+ private static final String TRANSPARENCY_INFO_OD_PATH =
+ "com/android/asllib/transparencyinfo/od";
+
+ private static final String VALID_EMPTY_FILE_NAME = "valid-empty.xml";
+ private static final String WITH_DEVELOPER_INFO_FILE_NAME = "with-developer-info.xml";
+ private static final String WITH_APP_INFO_FILE_NAME = "with-app-info.xml";
+
+ private Document mDoc = null;
+
+ @Before
+ public void setUp() throws Exception {
+ System.out.println("set up.");
+ mDoc = TestUtils.document();
+ }
+
+ /** Test for transparency info valid empty. */
+ @Test
+ public void testTransparencyInfoValidEmptyFile() throws Exception {
+ System.out.println("starting testTransparencyInfoValidEmptyFile.");
+ testHrToOdTransparencyInfo(VALID_EMPTY_FILE_NAME);
+ }
+
+ /** Test for transparency info with developer info. */
+ @Test
+ public void testTransparencyInfoWithDeveloperInfo() throws Exception {
+ System.out.println("starting testTransparencyInfoWithDeveloperInfo.");
+ testHrToOdTransparencyInfo(WITH_DEVELOPER_INFO_FILE_NAME);
+ }
+
+ /** Test for transparency info with app info. */
+ @Test
+ public void testTransparencyInfoWithAppInfo() throws Exception {
+ System.out.println("starting testTransparencyInfoWithAppInfo.");
+ testHrToOdTransparencyInfo(WITH_APP_INFO_FILE_NAME);
+ }
+
+ private void testHrToOdTransparencyInfo(String fileName) throws Exception {
+ TestUtils.testHrToOd(
+ mDoc,
+ new TransparencyInfoFactory(),
+ TRANSPARENCY_INFO_HR_PATH,
+ TRANSPARENCY_INFO_OD_PATH,
+ fileName);
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
new file mode 100644
index 0000000..faea340
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/java/com/android/asllib/testutils/TestUtils.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 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.asllib.testutils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import com.android.asllib.marshallable.AslMarshallable;
+import com.android.asllib.marshallable.AslMarshallableFactory;
+import com.android.asllib.util.MalformedXmlException;
+import com.android.asllib.util.XmlUtils;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+public class TestUtils {
+ public static final String HOLDER_TAG_NAME = "holder_of_flattened_for_testing";
+
+ /** Reads a Resource file into a String. */
+ public static String readStrFromResource(Path filePath) throws IOException {
+ InputStream hrStream =
+ TestUtils.class.getClassLoader().getResourceAsStream(filePath.toString());
+ return new String(hrStream.readAllBytes(), StandardCharsets.UTF_8);
+ }
+
+ /** Gets List of Element from a path to an existing Resource. */
+ public static List<Element> getElementsFromResource(Path filePath)
+ throws ParserConfigurationException, IOException, SAXException {
+ String str = readStrFromResource(filePath);
+ InputStream stream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8));
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ Document document = factory.newDocumentBuilder().parse(stream);
+ Element root = document.getDocumentElement();
+ if (root.getTagName().equals(HOLDER_TAG_NAME)) {
+ String tagName =
+ XmlUtils.asElementList(root.getChildNodes()).stream()
+ .findFirst()
+ .get()
+ .getTagName();
+ return XmlUtils.asElementList(root.getElementsByTagName(tagName));
+ } else {
+ return List.of(root);
+ }
+ }
+
+ /** Reads a Document into a String. */
+ public static String docToStr(Document doc, boolean omitXmlDeclaration)
+ throws TransformerException {
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ transformer.setOutputProperty(
+ OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration ? "yes" : "no");
+
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ StreamResult streamResult = new StreamResult(outStream); // out
+ DOMSource domSource = new DOMSource(doc);
+ transformer.transform(domSource, streamResult);
+
+ return outStream.toString(StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Gets formatted XML for slightly more robust comparison checking than naive string comparison.
+ */
+ public static String getFormattedXml(String xmlStr, boolean omitXmlDeclaration)
+ throws ParserConfigurationException, IOException, SAXException, TransformerException {
+ InputStream stream = new ByteArrayInputStream(xmlStr.getBytes(StandardCharsets.UTF_8));
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ Document document = factory.newDocumentBuilder().parse(stream);
+
+ return docToStr(document, omitXmlDeclaration);
+ }
+
+ /** Helper for getting a new Document */
+ public static Document document() throws ParserConfigurationException {
+ return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ }
+
+ /** Helper for testing human-readable to on-device conversion expecting exception */
+ public static <T extends AslMarshallable> void hrToOdExpectException(
+ AslMarshallableFactory<T> factory, String hrFolderPath, String fileName) {
+ assertThrows(
+ MalformedXmlException.class,
+ () -> {
+ factory.createFromHrElements(
+ TestUtils.getElementsFromResource(Paths.get(hrFolderPath, fileName)));
+ });
+ }
+
+ /** Helper for testing human-readable to on-device conversion */
+ public static <T extends AslMarshallable> void testHrToOd(
+ Document doc,
+ AslMarshallableFactory<T> factory,
+ String hrFolderPath,
+ String odFolderPath,
+ String fileName)
+ throws Exception {
+ AslMarshallable marshallable =
+ factory.createFromHrElements(
+ TestUtils.getElementsFromResource(Paths.get(hrFolderPath, fileName)));
+
+ for (var child : marshallable.toOdDomElements(doc)) {
+ doc.appendChild(child);
+ }
+ String converted = TestUtils.docToStr(doc, true);
+ System.out.println("converted: " + converted);
+
+ String expectedOdContents =
+ TestUtils.readStrFromResource(Paths.get(odFolderPath, fileName));
+ assertEquals(
+ TestUtils.getFormattedXml(expectedOdContents, true),
+ TestUtils.getFormattedXml(converted, true));
+ }
+}
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml
new file mode 100644
index 0000000..ec0cd70
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/missing-version.xml
@@ -0,0 +1,3 @@
+<app-metadata-bundles>
+
+</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml
new file mode 100644
index 0000000..19bfd82
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/valid-empty.xml
@@ -0,0 +1 @@
+<app-metadata-bundles version="123456"></app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
new file mode 100644
index 0000000..53794a1
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-safety-labels.xml
@@ -0,0 +1,4 @@
+<app-metadata-bundles version="123456">
+ <safety-labels version="12345">
+ </safety-labels>
+</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml
new file mode 100644
index 0000000..7bcde45
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-system-app-safety-label.xml
@@ -0,0 +1,4 @@
+<app-metadata-bundles version="123456">
+<system-app-safety-label url="www.example.com">
+</system-app-safety-label>
+</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml
new file mode 100644
index 0000000..00bcfa8
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/hr/with-transparency-info.xml
@@ -0,0 +1,4 @@
+<app-metadata-bundles version="123456">
+<transparency-info>
+</transparency-info>
+</app-metadata-bundles>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml
new file mode 100644
index 0000000..37bdfad
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/valid-empty.xml
@@ -0,0 +1,3 @@
+<bundle>
+ <long name="version" value="123456"/>
+</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
new file mode 100644
index 0000000..74644ed
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-safety-labels.xml
@@ -0,0 +1,6 @@
+<bundle>
+ <long name="version" value="123456"/>
+ <pbundle_as_map name="safety_labels">
+ <long name="version" value="12345"/>
+ </pbundle_as_map>
+</bundle>
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml
new file mode 100644
index 0000000..ef0f549
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-system-app-safety-label.xml
@@ -0,0 +1,6 @@
+<bundle>
+ <long name="version" value="123456"/>
+ <pbundle_as_map name="system_app_safety_label">
+ <string name="url" value="www.example.com"/>
+ </pbundle_as_map>
+</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml
new file mode 100644
index 0000000..63c5094
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/androidsafetylabel/od/with-transparency-info.xml
@@ -0,0 +1,4 @@
+<bundle>
+ <long name="version" value="123456"/>
+ <pbundle_as_map name="transparency_info"/>
+</bundle>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
new file mode 100644
index 0000000..883170a2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/hr/all-fields-valid.xml
@@ -0,0 +1,14 @@
+<app-info
+ title="beervision"
+ description="a beer app"
+ containsAds="true"
+ obeyAps="false"
+ adsFingerprinting="false"
+ securityFingerprinting="false"
+ privacyPolicy="www.example.com"
+ securityEndpoints="url1|url2|url3"
+ firstPartyEndpoints="url1"
+ serviceProviderEndpoints="url55|url56"
+ category="Food and drink"
+ email="max@maxloh.com"
+ website="www.example.com" />
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
new file mode 100644
index 0000000..6e976a3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/appinfo/od/all-fields-valid.xml
@@ -0,0 +1,25 @@
+
+<pbundle_as_map name="app_info">
+ <string name="title" value="beervision"/>
+ <string name="description" value="a beer app"/>
+ <boolean name="contains_ads" value="true"/>
+ <boolean name="obey_aps" value="false"/>
+ <boolean name="ads_fingerprinting" value="false"/>
+ <boolean name="security_fingerprinting" value="false"/>
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="security_endpoint" num="3">
+ <item value="url1"/>
+ <item value="url2"/>
+ <item value="url3"/>
+ </string-array>
+ <string-array name="first_party_endpoint" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoint" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <string name="category" value="Food and drink"/>
+ <string name="email" value="max@maxloh.com"/>
+ <string name="website" value="www.example.com"/>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-actions-in-app.xml
new file mode 100644
index 0000000..520e525
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-actions-in-app.xml
@@ -0,0 +1,17 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="actions_in_app"
+ dataType="user_interaction"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="in_app_search_history"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="installed_apps"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="user_generated_content"
+ purposes="analytics" />
+ <data-shared dataCategory="actions_in_app"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-app-performance.xml
new file mode 100644
index 0000000..0d08e5b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-app-performance.xml
@@ -0,0 +1,11 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="app_performance"
+ dataType="crash_logs"
+ purposes="analytics" />
+ <data-shared dataCategory="app_performance"
+ dataType="performance_diagnostics"
+ purposes="analytics" />
+ <data-shared dataCategory="app_performance"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-audio.xml
new file mode 100644
index 0000000..b1cf3b4
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-audio.xml
@@ -0,0 +1,11 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="audio"
+ dataType="sound_recordings"
+ purposes="analytics" />
+ <data-shared dataCategory="audio"
+ dataType="music_files"
+ purposes="analytics" />
+ <data-shared dataCategory="audio"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-calendar.xml
new file mode 100644
index 0000000..a723070
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-calendar.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="calendar"
+ dataType="calendar"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-contacts.xml
new file mode 100644
index 0000000..2fe28ff
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-contacts.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="contacts"
+ dataType="contacts"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-email-text-message.xml
new file mode 100644
index 0000000..49a326f
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-email-text-message.xml
@@ -0,0 +1,11 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="email_text_message"
+ dataType="emails"
+ purposes="analytics" />
+ <data-shared dataCategory="email_text_message"
+ dataType="text_messages"
+ purposes="analytics" />
+ <data-shared dataCategory="email_text_message"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-financial.xml
new file mode 100644
index 0000000..f5de370
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-financial.xml
@@ -0,0 +1,14 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="financial"
+ dataType="card_bank_account"
+ purposes="analytics" />
+ <data-shared dataCategory="financial"
+ dataType="purchase_history"
+ purposes="analytics" />
+ <data-shared dataCategory="financial"
+ dataType="credit_score"
+ purposes="analytics" />
+ <data-shared dataCategory="financial"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-health-fitness.xml
new file mode 100644
index 0000000..9891f81
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-health-fitness.xml
@@ -0,0 +1,8 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="health_fitness"
+ dataType="health"
+ purposes="analytics" />
+ <data-shared dataCategory="health_fitness"
+ dataType="fitness"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-identifiers.xml
new file mode 100644
index 0000000..3e74da1
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-identifiers.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="identifiers"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-location.xml
new file mode 100644
index 0000000..4762f16
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-location.xml
@@ -0,0 +1,8 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ purposes="analytics" />
+ <data-shared dataCategory="location"
+ dataType="precise_location"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-empty-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-empty-purpose.xml
new file mode 100644
index 0000000..964e178
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-empty-purpose.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="personal"
+ dataType="email_address"
+ purposes="" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-missing-purpose.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-missing-purpose.xml
new file mode 100644
index 0000000..3ce1288
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-missing-purpose.xml
@@ -0,0 +1,4 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="personal"
+ dataType="email_address" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-partial.xml
new file mode 100644
index 0000000..68baae3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-partial.xml
@@ -0,0 +1,8 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="personal"
+ dataType="name"
+ purposes="analytics|developer_communications" />
+ <data-shared dataCategory="personal"
+ dataType="email_address"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-unrecognized-type.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-unrecognized-type.xml
new file mode 100644
index 0000000..921a90a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal-unrecognized-type.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="personal"
+ dataType="unrecognized"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal.xml
new file mode 100644
index 0000000..4533773
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-personal.xml
@@ -0,0 +1,31 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="personal"
+ dataType="name"
+ ephemeral="true"
+ isCollectionOptional="true"
+ purposes="analytics|developer_communications" />
+ <data-shared dataCategory="personal"
+ dataType="email_address"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="physical_address"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="phone_number"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="race_ethnicity"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="political_or_religious_beliefs"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="sexual_orientation_or_gender_identity"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="personal_identifiers"
+ purposes="analytics" />
+ <data-shared dataCategory="personal"
+ dataType="other"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-photo-video.xml
new file mode 100644
index 0000000..234fb26
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-photo-video.xml
@@ -0,0 +1,8 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="photo_video"
+ dataType="photos"
+ purposes="analytics" />
+ <data-shared dataCategory="photo_video"
+ dataType="videos"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-search-and-browsing.xml
new file mode 100644
index 0000000..db85163
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-search-and-browsing.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="search_and_browsing"
+ dataType="web_browsing_history"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-storage.xml
new file mode 100644
index 0000000..9aad02d
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-storage.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="storage"
+ dataType="files_docs"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-unrecognized.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-unrecognized.xml
new file mode 100644
index 0000000..64b9ea7
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/hr/data-category-unrecognized.xml
@@ -0,0 +1,5 @@
+<holder_of_flattened_for_testing>
+ <data-shared dataCategory="unrecognized"
+ dataType="email_address"
+ purposes="analytics" />
+</holder_of_flattened_for_testing>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-actions-in-app.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-actions-in-app.xml
new file mode 100644
index 0000000..5b99900
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-actions-in-app.xml
@@ -0,0 +1,27 @@
+<pbundle_as_map name="actions_in_app">
+ <pbundle_as_map name="user_interaction">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="in_app_search_history">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="installed_apps">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="user_generated_content">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-app-performance.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-app-performance.xml
new file mode 100644
index 0000000..0fe1022
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-app-performance.xml
@@ -0,0 +1,17 @@
+<pbundle_as_map name="app_performance">
+ <pbundle_as_map name="crash_logs">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="performance_diagnostics">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-audio.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-audio.xml
new file mode 100644
index 0000000..51f1dfd
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-audio.xml
@@ -0,0 +1,17 @@
+<pbundle_as_map name="audio">
+ <pbundle_as_map name="sound_recordings">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="music_files">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-calendar.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-calendar.xml
new file mode 100644
index 0000000..326da47
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-calendar.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="calendar">
+ <pbundle_as_map name="calendar">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-contacts.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-contacts.xml
new file mode 100644
index 0000000..5d4387d
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-contacts.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="contacts">
+ <pbundle_as_map name="contacts">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-email-text-message.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-email-text-message.xml
new file mode 100644
index 0000000..5ac98f5
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-email-text-message.xml
@@ -0,0 +1,17 @@
+<pbundle_as_map name="email_text_message">
+ <pbundle_as_map name="emails">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="text_messages">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-financial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-financial.xml
new file mode 100644
index 0000000..a66f1a4
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-financial.xml
@@ -0,0 +1,22 @@
+<pbundle_as_map name="financial">
+ <pbundle_as_map name="card_bank_account">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="purchase_history">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="credit_score">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-health-fitness.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-health-fitness.xml
new file mode 100644
index 0000000..8e697b4
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-health-fitness.xml
@@ -0,0 +1,12 @@
+<pbundle_as_map name="health_fitness">
+ <pbundle_as_map name="health">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="fitness">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-identifiers.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-identifiers.xml
new file mode 100644
index 0000000..34b4016
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-identifiers.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="identifiers">
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-location.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-location.xml
new file mode 100644
index 0000000..db2e696
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-location.xml
@@ -0,0 +1,12 @@
+<pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="precise_location">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal-partial.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal-partial.xml
new file mode 100644
index 0000000..839922a
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal-partial.xml
@@ -0,0 +1,13 @@
+<pbundle_as_map name="personal">
+ <pbundle_as_map name="name">
+ <int-array name="purposes" num="2">
+ <item value="2" />
+ <item value="3" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="email_address">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal.xml
new file mode 100644
index 0000000..43650b6
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-personal.xml
@@ -0,0 +1,50 @@
+<pbundle_as_map name="personal">
+ <pbundle_as_map name="name">
+ <int-array name="purposes" num="2">
+ <item value="2" />
+ <item value="3" />
+ </int-array>
+ <boolean name="is_collection_optional" value="true" />
+ <boolean name="ephemeral" value="true" />
+ </pbundle_as_map>
+ <pbundle_as_map name="email_address">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="physical_address">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="phone_number">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="race_ethnicity">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="political_or_religious_beliefs">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="sexual_orientation_or_gender_identity">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="personal_identifiers">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="other">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-photo-video.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-photo-video.xml
new file mode 100644
index 0000000..2a31780
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-photo-video.xml
@@ -0,0 +1,12 @@
+<pbundle_as_map name="photo_video">
+ <pbundle_as_map name="photos">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+ <pbundle_as_map name="videos">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-search-and-browsing.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-search-and-browsing.xml
new file mode 100644
index 0000000..9e654ef
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-search-and-browsing.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="search_and_browsing">
+ <pbundle_as_map name="web_browsing_history">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-storage.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-storage.xml
new file mode 100644
index 0000000..9abc37f
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datacategory/od/data-category-storage.xml
@@ -0,0 +1,7 @@
+<pbundle_as_map name="storage">
+ <pbundle_as_map name="files_docs">
+ <int-array name="purposes" num="1">
+ <item value="2" />
+ </int-array>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml
new file mode 100644
index 0000000..bb45f42
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-invalid-bool.xml
@@ -0,0 +1,7 @@
+<data-labels>
+ <data-accessed dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ isSharingOptional="false"
+ purposes="app_functionality" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml
new file mode 100644
index 0000000..f927bba
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-accessed-valid-bool.xml
@@ -0,0 +1,6 @@
+<data-labels>
+ <data-accessed dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ purposes="app_functionality" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml
new file mode 100644
index 0000000..ba11afb
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-invalid-bool.xml
@@ -0,0 +1,7 @@
+<data-labels>
+ <data-collected dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ isSharingOptional="false"
+ purposes="app_functionality" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml
new file mode 100644
index 0000000..4b6d3977
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-collected-valid-bool.xml
@@ -0,0 +1,7 @@
+<data-labels>
+ <data-collected dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ isCollectionOptional="false"
+ purposes="app_functionality" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml
new file mode 100644
index 0000000..7840b98
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-invalid-bool.xml
@@ -0,0 +1,7 @@
+<data-labels>
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ isCollectionOptional="false"
+ purposes="app_functionality" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml
new file mode 100644
index 0000000..ccf77b0
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/hr/data-labels-shared-valid-bool.xml
@@ -0,0 +1,7 @@
+<data-labels>
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ ephemeral="false"
+ isSharingOptional="false"
+ purposes="app_functionality" />
+</data-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml
new file mode 100644
index 0000000..ddefc18
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-accessed-valid-bool.xml
@@ -0,0 +1,12 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_accessed">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-valid-bool.xml
new file mode 100644
index 0000000..252c728
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-collected-valid-bool.xml
@@ -0,0 +1,13 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_collected">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="is_collection_optional" value="false"/>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
new file mode 100644
index 0000000..d1d4e33
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/datalabels/od/data-labels-shared-valid-bool.xml
@@ -0,0 +1,13 @@
+<pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="false"/>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/hr/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/hr/all-fields-valid.xml
new file mode 100644
index 0000000..908d8ea2
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/hr/all-fields-valid.xml
@@ -0,0 +1,8 @@
+<developer-info
+ name="max"
+ email="max@example.com"
+ address="111 blah lane"
+ countryRegion="US"
+ relationship="aosp"
+ website="example.com"
+ registryId="registry_id" />
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/od/all-fields-valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/od/all-fields-valid.xml
new file mode 100644
index 0000000..784ec61
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/developerinfo/od/all-fields-valid.xml
@@ -0,0 +1,9 @@
+<pbundle_as_map name="developer_info">
+ <string name="name" value="max"/>
+ <string name="email" value="max@example.com"/>
+ <string name="address" value="111 blah lane"/>
+ <string name="country_region" value="US"/>
+ <long name="relationship" value="5"/>
+ <string name="website" value="example.com"/>
+ <string name="app_developer_registry_id" value="registry_id"/>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml
new file mode 100644
index 0000000..762f3bd
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/missing-version.xml
@@ -0,0 +1,2 @@
+<safety-labels>
+</safety-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml
new file mode 100644
index 0000000..7decfd4
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/valid-empty.xml
@@ -0,0 +1 @@
+<safety-labels version="12345"></safety-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml
new file mode 100644
index 0000000..8997f4f
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/hr/with-data-labels.xml
@@ -0,0 +1,9 @@
+<safety-labels version="12345">
+ <data-labels>
+ <data-shared dataCategory="location"
+ dataType="approx_location"
+ isSharingOptional="false"
+ ephemeral="false"
+ purposes="app_functionality" />
+ </data-labels>
+</safety-labels>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml
new file mode 100644
index 0000000..4f03d88
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/valid-empty.xml
@@ -0,0 +1,3 @@
+<pbundle_as_map name="safety_labels">
+ <long name="version" value="12345"/>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml
new file mode 100644
index 0000000..a966fda
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/safetylabels/od/with-data-labels.xml
@@ -0,0 +1,16 @@
+<pbundle_as_map name="safety_labels">
+ <long name="version" value="12345"/>
+ <pbundle_as_map name="data_labels">
+ <pbundle_as_map name="data_shared">
+ <pbundle_as_map name="location">
+ <pbundle_as_map name="approx_location">
+ <int-array name="purposes" num="1">
+ <item value="1"/>
+ </int-array>
+ <boolean name="is_sharing_optional" value="false"/>
+ <boolean name="ephemeral" value="false"/>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml
new file mode 100644
index 0000000..ff26c05
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/missing-url.xml
@@ -0,0 +1 @@
+<system-app-safety-label></system-app-safety-label>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml
new file mode 100644
index 0000000..6fe86c3
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/hr/valid.xml
@@ -0,0 +1 @@
+<system-app-safety-label url="www.example.com"></system-app-safety-label>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml
new file mode 100644
index 0000000..f96535b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/systemappsafetylabel/od/valid.xml
@@ -0,0 +1,3 @@
+<pbundle_as_map name="system_app_safety_label">
+ <string name="url" value="www.example.com"/>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/valid-empty.xml
new file mode 100644
index 0000000..254a37f
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/valid-empty.xml
@@ -0,0 +1,4 @@
+
+<transparency-info>
+
+</transparency-info>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
new file mode 100644
index 0000000..a7c48fc
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-app-info.xml
@@ -0,0 +1,4 @@
+
+<transparency-info>
+ <app-info title="beervision" description="a beer app" containsAds="true" obeyAps="false" adsFingerprinting="false" securityFingerprinting="false" privacyPolicy="www.example.com" securityEndpoints="url1|url2|url3" firstPartyEndpoints="url1" serviceProviderEndpoints="url55|url56" category="Food and drink" email="max@maxloh.com" />
+</transparency-info>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
new file mode 100644
index 0000000..862bda4
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/hr/with-developer-info.xml
@@ -0,0 +1,11 @@
+
+<transparency-info>
+ <developer-info
+ name="max"
+ email="max@example.com"
+ address="111 blah lane"
+ countryRegion="US"
+ relationship="aosp"
+ website="example.com"
+ appDeveloperRegistryId="registry_id" />
+</transparency-info>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-empty.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-empty.xml
new file mode 100644
index 0000000..af574cf
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/valid-empty.xml
@@ -0,0 +1 @@
+<pbundle_as_map name="transparency_info"/>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
new file mode 100644
index 0000000..b813641
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-app-info.xml
@@ -0,0 +1,26 @@
+
+<pbundle_as_map name="transparency_info">
+ <pbundle_as_map name="app_info">
+ <string name="title" value="beervision"/>
+ <string name="description" value="a beer app"/>
+ <boolean name="contains_ads" value="true"/>
+ <boolean name="obey_aps" value="false"/>
+ <boolean name="ads_fingerprinting" value="false"/>
+ <boolean name="security_fingerprinting" value="false"/>
+ <string name="privacy_policy" value="www.example.com"/>
+ <string-array name="security_endpoint" num="3">
+ <item value="url1"/>
+ <item value="url2"/>
+ <item value="url3"/>
+ </string-array>
+ <string-array name="first_party_endpoint" num="1">
+ <item value="url1"/>
+ </string-array>
+ <string-array name="service_provider_endpoint" num="2">
+ <item value="url55"/>
+ <item value="url56"/>
+ </string-array>
+ <string name="category" value="Food and drink"/>
+ <string name="email" value="max@maxloh.com"/>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
new file mode 100644
index 0000000..101c98b
--- /dev/null
+++ b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/transparencyinfo/od/with-developer-info.xml
@@ -0,0 +1,11 @@
+
+<pbundle_as_map name="transparency_info">
+ <pbundle_as_map name="developer_info">
+ <string name="name" value="max"/>
+ <string name="email" value="max@example.com"/>
+ <string name="address" value="111 blah lane"/>
+ <string name="country_region" value="US"/>
+ <long name="relationship" value="5"/>
+ <string name="website" value="example.com"/>
+ </pbundle_as_map>
+</pbundle_as_map>
\ No newline at end of file
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml
similarity index 100%
rename from tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/hr.xml
rename to tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/hr.xml
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml
similarity index 100%
rename from tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/contacts/od.xml
rename to tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/contacts/od.xml
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/hr.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml
similarity index 100%
rename from tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/hr.xml
rename to tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/hr.xml
diff --git a/tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/od.xml b/tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml
similarity index 100%
rename from tools/app_metadata_bundles/src/test/resources/com/android/aslgen/validmappings/location/od.xml
rename to tools/app_metadata_bundles/src/test/resources/com/android/asllib/validmappings/location/od.xml
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 837dae9..0f1373c 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -24,11 +24,20 @@
import com.github.javaparser.ParserConfiguration
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.Modifier
import com.github.javaparser.ast.NodeList
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.body.InitializerDeclaration
+import com.github.javaparser.ast.expr.ArrayAccessExpr
+import com.github.javaparser.ast.expr.ArrayCreationExpr
+import com.github.javaparser.ast.expr.ArrayInitializerExpr
+import com.github.javaparser.ast.expr.AssignExpr
+import com.github.javaparser.ast.expr.BooleanLiteralExpr
+import com.github.javaparser.ast.expr.Expression
import com.github.javaparser.ast.expr.FieldAccessExpr
+import com.github.javaparser.ast.expr.IntegerLiteralExpr
import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.MethodReferenceExpr
import com.github.javaparser.ast.expr.NameExpr
import com.github.javaparser.ast.expr.NullLiteralExpr
import com.github.javaparser.ast.expr.ObjectCreationExpr
@@ -168,6 +177,8 @@
val classNameNode = classDeclaration.findFirst(SimpleName::class.java).get()
classNameNode.setId(protoLogImplGenName)
+ injectCacheClass(classDeclaration, groups, protoLogGroupsClassName)
+
injectConstants(classDeclaration,
viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath, groups,
protoLogGroupsClassName)
@@ -238,6 +249,12 @@
field.setFinal(true)
field.variables.first().setInitializer(treeMapCreation)
}
+ ProtoLogToolInjected.Value.CACHE_UPDATER.name -> {
+ field.setFinal(true)
+ field.variables.first().setInitializer(MethodReferenceExpr()
+ .setScope(NameExpr("Cache"))
+ .setIdentifier("update"))
+ }
else -> error("Unhandled ProtoLogToolInjected value: $valueName.")
}
}
@@ -245,6 +262,61 @@
}
}
+ private fun injectCacheClass(
+ classDeclaration: ClassOrInterfaceDeclaration,
+ groups: Map<String, LogGroup>,
+ protoLogGroupsClassName: String,
+ ) {
+ val cacheClass = ClassOrInterfaceDeclaration()
+ .setName("Cache")
+ .setPublic(true)
+ .setStatic(true)
+ for (group in groups) {
+ val nodeList = NodeList<Expression>()
+ val defaultVal = BooleanLiteralExpr(group.value.textEnabled || group.value.enabled)
+ repeat(LogLevel.entries.size) { nodeList.add(defaultVal) }
+ cacheClass.addFieldWithInitializer(
+ "boolean[]",
+ "${group.key}_enabled",
+ ArrayCreationExpr().setElementType("boolean[]").setInitializer(
+ ArrayInitializerExpr().setValues(nodeList)
+ ),
+ Modifier.Keyword.PUBLIC,
+ Modifier.Keyword.STATIC
+ )
+ }
+
+ val updateBlockStmt = BlockStmt()
+ for (group in groups) {
+ for (level in LogLevel.entries) {
+ updateBlockStmt.addStatement(
+ AssignExpr()
+ .setTarget(
+ ArrayAccessExpr()
+ .setName(NameExpr("${group.key}_enabled"))
+ .setIndex(IntegerLiteralExpr(level.ordinal))
+ ).setValue(
+ MethodCallExpr()
+ .setName("isEnabled")
+ .setArguments(NodeList(
+ FieldAccessExpr()
+ .setScope(NameExpr(protoLogGroupsClassName))
+ .setName(group.value.name),
+ FieldAccessExpr()
+ .setScope(NameExpr("LogLevel"))
+ .setName(level.toString()),
+ ))
+ )
+ )
+ }
+ }
+
+ cacheClass.addMethod("update").setPrivate(true).setStatic(true)
+ .setBody(updateBlockStmt)
+
+ classDeclaration.addMember(cacheClass)
+ }
+
private fun tryParse(code: String, fileName: String): CompilationUnit {
try {
return StaticJavaParser.parse(code)
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index 2b71641..6a8a071 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -22,6 +22,7 @@
import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.NodeList
import com.github.javaparser.ast.body.VariableDeclarator
+import com.github.javaparser.ast.expr.ArrayAccessExpr
import com.github.javaparser.ast.expr.CastExpr
import com.github.javaparser.ast.expr.Expression
import com.github.javaparser.ast.expr.FieldAccessExpr
@@ -35,6 +36,8 @@
import com.github.javaparser.ast.expr.VariableDeclarationExpr
import com.github.javaparser.ast.stmt.BlockStmt
import com.github.javaparser.ast.stmt.ExpressionStmt
+import com.github.javaparser.ast.stmt.IfStmt
+import com.github.javaparser.ast.stmt.Statement
import com.github.javaparser.ast.type.ArrayType
import com.github.javaparser.ast.type.ClassOrInterfaceType
import com.github.javaparser.ast.type.PrimitiveType
@@ -74,6 +77,8 @@
private val protoLogImplClassNode =
StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName)
+ private val protoLogImplCacheClassNode =
+ StaticJavaParser.parseExpression<FieldAccessExpr>("$protoLogImplClassName.Cache")
private var processedCode: MutableList<String> = mutableListOf()
private var offsets: IntArray = IntArray(0)
/** The path of the file being processed, relative to $ANDROID_BUILD_TOP */
@@ -121,8 +126,9 @@
group: LogGroup,
level: LogLevel,
messageString: String
- ): BlockStmt {
+ ): Statement {
val hash = CodeUtils.hash(packagePath, messageString, level, group)
+
val newCall = call.clone()
if (!group.textEnabled) {
// Remove message string if text logging is not enabled by default.
@@ -166,11 +172,15 @@
}
blockStmt.addStatement(ExpressionStmt(newCall))
- return blockStmt
+ val isLogEnabled = ArrayAccessExpr()
+ .setName(NameExpr("$protoLogImplCacheClassNode.${group.name}_enabled"))
+ .setIndex(IntegerLiteralExpr(level.ordinal))
+
+ return IfStmt(isLogEnabled, blockStmt, null)
}
private fun injectProcessedCallStatementInCode(
- processedCallStatement: BlockStmt,
+ processedCallStatement: Statement,
parentStmt: ExpressionStmt
) {
// Inline the new statement.
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index de0b5ba..82aa93d 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -76,7 +76,7 @@
class Test {
void test() {
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -86,7 +86,7 @@
class Test {
void test() {
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
}
}
@@ -98,8 +98,8 @@
class Test {
void test() {
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -109,7 +109,7 @@
class Test {
void test() {
- { org.example.ProtoLogImpl.w(TEST_GROUP, 3218600869538902408L, 0, "test", (Object[]) null); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { org.example.ProtoLogImpl.w(TEST_GROUP, 3218600869538902408L, 0, "test", (Object[]) null); }
}
}
""".trimIndent()
@@ -119,7 +119,7 @@
class Test {
void test() {
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, null, protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, null, protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -129,7 +129,7 @@
class Test {
void test() {
- { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
}
}